Skip to main content

relibc/pthread/
mod.rs

1//! Relibc Threads, or RLCT.
2
3use core::{
4    cell::UnsafeCell,
5    ptr,
6    sync::atomic::{AtomicBool, AtomicUsize, Ordering},
7};
8
9use alloc::collections::BTreeMap;
10
11use crate::{
12    error::Errno,
13    header::{errno::*, pthread as header, sched::sched_param, sys_mman},
14    ld_so::{ExpectTlsFree, tcb::Tcb},
15    platform::{Pal, Sys, types::*},
16};
17
18use crate::sync::{Mutex, waitval::Waitval};
19
20/// Called only by the main thread, as part of relibc_start.
21#[allow(unused_mut)]
22pub unsafe fn init() {
23    let mut thread = Pthread {
24        waitval: Waitval::new(),
25        has_enabled_cancelation: AtomicBool::new(false),
26        has_queued_cancelation: AtomicBool::new(false),
27        flags: PthreadFlags::empty().bits().into(),
28
29        //index: FIRST_THREAD_IDX,
30
31        // TODO: set these values on Linux as well
32        stack_base: ptr::null_mut(),
33        stack_size: 0,
34
35        os_tid: UnsafeCell::new(Sys::current_os_tid()),
36    };
37
38    #[cfg(target_os = "redox")]
39    {
40        //TODO: what is the best way to get these values?
41        use redox_rt::arch::{STACK_SIZE, STACK_TOP};
42        thread.stack_base = (STACK_TOP - STACK_SIZE) as *mut c_void;
43        thread.stack_size = STACK_SIZE;
44    }
45
46    unsafe { Tcb::current() }
47        .expect_notls("no TCB present for main thread")
48        .pthread = thread;
49}
50
51//static NEXT_INDEX: AtomicU32 = AtomicU32::new(FIRST_THREAD_IDX + 1);
52//const FIRST_THREAD_IDX: usize = 1;
53
54pub unsafe fn terminate_from_main_thread() {
55    for tcb in OS_TID_TO_PTHREAD.lock().values() {
56        let _ = unsafe { cancel(&(*tcb.0).pthread) };
57    }
58}
59
60bitflags::bitflags! {
61    pub struct PthreadFlags: usize {
62        const DETACHED = 1;
63    }
64}
65
66#[derive(Debug)]
67pub struct Pthread {
68    pub(crate) waitval: Waitval<Retval>,
69    pub(crate) has_queued_cancelation: AtomicBool,
70    pub(crate) has_enabled_cancelation: AtomicBool,
71    pub(crate) flags: AtomicUsize,
72
73    pub(crate) stack_base: *mut c_void,
74    pub(crate) stack_size: usize,
75
76    pub os_tid: UnsafeCell<OsTid>,
77}
78
79#[derive(Clone, Copy, Debug, Default, Ord, Eq, PartialOrd, PartialEq)]
80pub struct OsTid {
81    #[cfg(target_os = "redox")]
82    pub thread_fd: usize,
83    #[cfg(target_os = "linux")]
84    pub thread_id: usize,
85}
86
87unsafe impl Send for Pthread {}
88unsafe impl Sync for Pthread {}
89
90#[derive(Clone, Copy, Debug)]
91pub struct Retval(pub *mut c_void);
92
93struct MmapGuard {
94    page_start: *mut c_void,
95    mmap_size: usize,
96}
97impl Drop for MmapGuard {
98    fn drop(&mut self) {
99        unsafe {
100            let _ = Sys::munmap(self.page_start, self.mmap_size);
101        }
102    }
103}
104
105#[allow(unused_mut)]
106pub(crate) unsafe fn create(
107    attrs: Option<&header::RlctAttr>,
108    start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void,
109    arg: *mut c_void,
110) -> Result<pthread_t, Errno> {
111    let attrs = attrs.cloned().unwrap_or_default();
112
113    #[cfg(not(target_os = "redox"))]
114    let mut current_sigmask = 0_u64;
115    #[cfg(target_os = "redox")]
116    let mut current_sigmask =
117        redox_rt::signal::get_sigmask().expect("failed to obtain sigprocmask for caller");
118
119    // Create a locked mutex, unlocked by the thread after it has started.
120    let synchronization_mutex = unsafe { Mutex::locked(current_sigmask) };
121    let synchronization_mutex = &synchronization_mutex;
122
123    let stack_size = attrs.stacksize.next_multiple_of(Sys::getpagesize());
124
125    let stack_base = if attrs.stack != 0 {
126        attrs.stack as *mut c_void
127    } else {
128        let ret = unsafe {
129            sys_mman::mmap(
130                core::ptr::null_mut(),
131                stack_size,
132                sys_mman::PROT_READ | sys_mman::PROT_WRITE,
133                sys_mman::MAP_PRIVATE | sys_mman::MAP_ANONYMOUS,
134                -1,
135                0,
136            )
137        };
138        if ret as isize == -1 {
139            // "Insufficient resources"
140            return Err(Errno(EAGAIN));
141        }
142        ret
143    };
144
145    let mut flags = PthreadFlags::empty();
146    match i32::from(attrs.detachstate) {
147        header::PTHREAD_CREATE_DETACHED => flags |= PthreadFlags::DETACHED,
148        header::PTHREAD_CREATE_JOINABLE => (),
149
150        other => unreachable!("unknown detachstate {}", other),
151    }
152
153    let stack_raii = MmapGuard {
154        page_start: stack_base,
155        mmap_size: stack_size,
156    };
157
158    let current_tcb = unsafe { Tcb::current() }.expect("no TCB!");
159    let new_tcb = unsafe { Tcb::new(current_tcb.tls_len) }.map_err(|_| Errno(ENOMEM))?;
160    new_tcb.pthread.flags = flags.bits().into();
161    new_tcb.pthread.stack_base = stack_base;
162    new_tcb.pthread.stack_size = stack_size;
163
164    new_tcb.masters_ptr = current_tcb.masters_ptr;
165    new_tcb.masters_len = current_tcb.masters_len;
166    new_tcb.linker_ptr = current_tcb.linker_ptr;
167    new_tcb.mspace = current_tcb.mspace;
168
169    let stack_end = unsafe { stack_base.add(stack_size) };
170    let mut stack = stack_end.cast::<usize>();
171    {
172        let mut push = |value: usize| {
173            stack = unsafe { stack.sub(1) };
174            unsafe { stack.write(value) };
175        };
176
177        if cfg!(target_arch = "aarch64") {
178            // Aarch64 requires the stack to be 16 byte aligned after
179            // the call instruction, unlike x86 which requires it to be
180            // aligned before the call instruction. As such push an
181            // extra word on the stack to align the stack to 16 bytes.
182            push(0);
183        }
184        push(0);
185        push(0);
186        push(ptr::from_ref(synchronization_mutex) as usize);
187        push(ptr::from_mut(new_tcb) as usize);
188
189        push(arg as usize);
190        push(start_routine as usize);
191
192        push(new_thread_shim as *const () as usize);
193    }
194
195    let Ok(os_tid) = (unsafe { Sys::rlct_clone(stack, &mut new_tcb.os_specific) }) else {
196        return Err(Errno(EAGAIN));
197    };
198    core::mem::forget(stack_raii);
199
200    let _ = synchronization_mutex.lock();
201
202    OS_TID_TO_PTHREAD
203        .lock()
204        .insert(os_tid, ForceSendSync(new_tcb));
205
206    Ok(&raw const new_tcb.pthread as *mut _)
207}
208
209/// A shim to wrap thread entry points in logic to set up TLS, for example
210unsafe extern "C" fn new_thread_shim(
211    entry_point: unsafe extern "C" fn(*mut c_void) -> *mut c_void,
212    arg: *mut c_void,
213    tcb: *mut Tcb,
214    synchronization_mutex: *const Mutex<u64>,
215) -> ! {
216    let tcb = unsafe { tcb.as_mut() }.expect_notls("non-null TLS is required");
217
218    #[cfg(not(target_os = "redox"))]
219    {
220        unsafe { tcb.activate() };
221    }
222    #[cfg(target_os = "redox")]
223    {
224        // `thr_fd` in `tcb` is set by [`Sys::rlct_clone`] *before* jumping to
225        // the entry point of the new thread.
226        unsafe {
227            tcb.activate(None);
228        }
229        redox_rt::signal::setup_sighandler(&tcb.os_specific, false);
230    }
231
232    let procmask = unsafe { (&*synchronization_mutex).as_ptr().read() };
233
234    unsafe { tcb.copy_masters() }.unwrap();
235
236    unsafe { tcb.pthread.os_tid.get().write(Sys::current_os_tid()) };
237
238    unsafe { (&*synchronization_mutex).manual_unlock() };
239
240    #[cfg(target_os = "redox")]
241    {
242        redox_rt::signal::set_sigmask(Some(procmask), None)
243            .expect("failed to set procmask in child thread");
244    }
245
246    let retval = unsafe { entry_point(arg) };
247
248    unsafe { exit_current_thread(Retval(retval)) }
249}
250pub unsafe fn join(thread: &Pthread) -> Result<Retval, Errno> {
251    // We don't have to return EDEADLK, but unlike e.g. pthread_t lifetime checking, it's a
252    // relatively easy check.
253    if core::ptr::eq(
254        thread,
255        current_thread().expect("current thread not present"),
256    ) {
257        return Err(Errno(EDEADLK));
258    }
259
260    // Waitval starts locked, and is unlocked when the thread finishes.
261    let retval = *thread.waitval.wait();
262
263    // We have now awaited the thread and received its return value. POSIX states that the
264    // pthread_t of this thread, will no longer be valid. In practice, we can thus deallocate the
265    // thread state.
266
267    unsafe { dealloc_thread(thread) };
268
269    Ok(retval)
270}
271
272pub unsafe fn detach(thread: &Pthread) -> Result<(), Errno> {
273    thread
274        .flags
275        .fetch_or(PthreadFlags::DETACHED.bits(), Ordering::AcqRel);
276    Ok(())
277}
278
279pub fn current_thread() -> Option<&'static Pthread> {
280    unsafe { Tcb::current().map(|p| &p.pthread) }
281}
282
283pub unsafe fn testcancel() {
284    let this_thread = current_thread().expect("current thread not present");
285
286    if this_thread.has_queued_cancelation.load(Ordering::Acquire)
287        && this_thread.has_enabled_cancelation.load(Ordering::Acquire)
288    {
289        unsafe { cancel_current_thread() };
290    }
291}
292
293pub unsafe fn exit_current_thread(retval: Retval) -> ! {
294    // Run pthread_cleanup_push/pthread_cleanup_pop destructors.
295    unsafe { header::run_destructor_stack() };
296
297    unsafe { header::tls::run_all_destructors() };
298
299    let this = current_thread().expect("failed to obtain current thread when exiting");
300    let stack_base = this.stack_base;
301    let stack_size = this.stack_size;
302
303    if this.flags.load(Ordering::Acquire) & PthreadFlags::DETACHED.bits() != 0 {
304        // When detached, the thread state no longer makes any sense, and can immediately be
305        // deallocated.
306        unsafe { dealloc_thread(this) };
307    } else {
308        // When joinable, the return value should be made available to other threads.
309        unsafe { this.waitval.post(retval) };
310    }
311
312    unsafe { Sys::exit_thread(stack_base.cast(), stack_size) }
313}
314
315unsafe fn dealloc_thread(thread: &Pthread) {
316    // TODO: How should this be handled on Linux?
317    unsafe {
318        OS_TID_TO_PTHREAD.lock().remove(&thread.os_tid.get().read());
319    }
320}
321pub const SIGRT_RLCT_CANCEL: usize = 33;
322pub const SIGRT_RLCT_TIMER: usize = 34;
323
324unsafe extern "C" fn cancel_sighandler(_: c_int) {
325    unsafe { cancel_current_thread() };
326}
327unsafe fn cancel_current_thread() {
328    // Terminate the thread
329    unsafe { exit_current_thread(Retval(header::PTHREAD_CANCELED)) };
330}
331
332pub unsafe fn cancel(thread: &Pthread) -> Result<(), Errno> {
333    // TODO: What order should these atomic bools be accessed in?
334    thread.has_queued_cancelation.store(true, Ordering::Release);
335
336    if thread.has_enabled_cancelation.load(Ordering::Acquire) {
337        (unsafe { Sys::rlct_kill(thread.os_tid.get().read(), SIGRT_RLCT_CANCEL) })?;
338    }
339
340    Ok(())
341}
342
343pub fn set_sched_param(
344    _thread: &Pthread,
345    _policy: c_int,
346    _param: &sched_param,
347) -> Result<(), Errno> {
348    // TODO
349    Ok(())
350}
351pub fn set_sched_priority(_thread: &Pthread, _prio: c_int) -> Result<(), Errno> {
352    // TODO
353    Ok(())
354}
355pub fn set_cancel_state(state: c_int) -> Result<c_int, Errno> {
356    let this_thread = current_thread().expect("current thread not present");
357
358    let was_cancelable = match state {
359        header::PTHREAD_CANCEL_ENABLE => {
360            let old = this_thread
361                .has_enabled_cancelation
362                .swap(true, Ordering::Release);
363
364            if this_thread.has_queued_cancelation.load(Ordering::Acquire) {
365                unsafe {
366                    cancel_current_thread();
367                }
368            }
369            old
370        }
371        header::PTHREAD_CANCEL_DISABLE => this_thread
372            .has_enabled_cancelation
373            .swap(false, Ordering::Release),
374
375        _ => return Err(Errno(EINVAL)),
376    };
377
378    Ok(match was_cancelable {
379        true => header::PTHREAD_CANCEL_ENABLE,
380        false => header::PTHREAD_CANCEL_DISABLE,
381    })
382}
383pub fn set_cancel_type(ty: c_int) -> Result<c_int, Errno> {
384    let this_thread = current_thread().expect("current thread not present");
385
386    // TODO
387    match ty {
388        header::PTHREAD_CANCEL_DEFERRED => (),
389        header::PTHREAD_CANCEL_ASYNCHRONOUS => (),
390
391        _ => return Err(Errno(EINVAL)),
392    }
393    Ok(header::PTHREAD_CANCEL_DEFERRED)
394}
395pub fn get_cpu_clkid(thread: &Pthread) -> Result<clockid_t, Errno> {
396    // TODO
397    Err(Errno(ENOENT))
398}
399pub fn get_sched_param(thread: &Pthread) -> Result<(clockid_t, sched_param), Errno> {
400    // TODO should be possible to return sched_param
401    Err(Errno(ENOSYS))
402}
403
404// TODO: Hash map?
405// TODO: RwLock to improve perf?
406static OS_TID_TO_PTHREAD: Mutex<BTreeMap<OsTid, ForceSendSync<*mut Tcb>>> =
407    Mutex::new(BTreeMap::new());
408
409#[derive(Clone, Copy)]
410struct ForceSendSync<T>(T);
411unsafe impl<T> Send for ForceSendSync<T> {}
412unsafe impl<T> Sync for ForceSendSync<T> {}
413
414/*pub(crate) fn current_thread_index() -> u32 {
415    current_thread().expect("current thread not present").index
416}*/
417
418#[derive(Clone, Copy, Default, Debug)]
419pub enum Pshared {
420    #[default]
421    Private,
422
423    Shared,
424}
425impl Pshared {
426    pub const fn from_raw(raw: c_int) -> Option<Self> {
427        Some(match raw {
428            header::PTHREAD_PROCESS_PRIVATE => Self::Private,
429            header::PTHREAD_PROCESS_SHARED => Self::Shared,
430
431            _ => return None,
432        })
433    }
434    pub const fn raw(self) -> c_int {
435        match self {
436            Self::Private => header::PTHREAD_PROCESS_PRIVATE,
437            Self::Shared => header::PTHREAD_PROCESS_SHARED,
438        }
439    }
440}