From f6eea754848ba1305ef4e319ff6c64626923e4b8 Mon Sep 17 00:00:00 2001 From: vibix auto-engineer Date: Tue, 5 May 2026 06:52:28 +0000 Subject: [PATCH 1/2] Implement sys/thread/vibix.rs and sys/sync/vibix/ (futex-based) in std fork Completes Phase 3 of the std-on-vibix effort by enabling std::thread::spawn, std::sync::Mutex, Condvar, RwLock, Once, and thread parking for vibix. Changes: - vibix_abi: add futex.rs (FUTEX_WAIT/WAKE wrappers) and thread.rs (clone, sched_yield, nanosleep, gettid, set_tid_address, join_thread) - std PAL: add sys/pal/vibix/futex.rs providing the Futex/SmallFutex types and futex_wait/futex_wake/futex_wake_all that sys/sync/ primitives need - std thread: add sys/thread/vibix.rs with Thread::new (clone-based spawn with mmap'd stack), Thread::join (futex wait on CHILD_CLEARTID), yield_now, sleep, available_parallelism, set_name (stub) - std sync: wire target_os = "vibix" into the futex branches of mutex, condvar, rwlock, once, and thread_parking cfg_select! dispatchers The std fork builds cleanly with: cargo +nightly -Z build-std=std,core,alloc,panic_abort \ -Z json-target-spec --target x86_64-unknown-vibix.json Closes #857 Co-Authored-By: Claude Opus 4.6 --- library/std/src/sys/pal/vibix/futex.rs | 89 ++++++ library/std/src/sys/pal/vibix/mod.rs | 2 + library/std/src/sys/sync/condvar/mod.rs | 1 + library/std/src/sys/sync/mutex/mod.rs | 1 + library/std/src/sys/sync/once/mod.rs | 1 + library/std/src/sys/sync/rwlock/mod.rs | 1 + .../std/src/sys/sync/thread_parking/mod.rs | 1 + library/std/src/sys/thread/mod.rs | 4 + library/std/src/sys/thread/vibix.rs | 297 ++++++++++++++++++ userspace/vibix_abi/src/futex.rs | 62 ++++ userspace/vibix_abi/src/lib.rs | 2 + userspace/vibix_abi/src/thread.rs | 127 ++++++++ 12 files changed, 588 insertions(+) create mode 100644 library/std/src/sys/pal/vibix/futex.rs create mode 100644 library/std/src/sys/thread/vibix.rs create mode 100644 userspace/vibix_abi/src/futex.rs create mode 100644 userspace/vibix_abi/src/thread.rs diff --git a/library/std/src/sys/pal/vibix/futex.rs b/library/std/src/sys/pal/vibix/futex.rs new file mode 100644 index 00000000..866b170a --- /dev/null +++ b/library/std/src/sys/pal/vibix/futex.rs @@ -0,0 +1,89 @@ +//! Futex implementation for vibix. +//! +//! This module provides the futex interface that `sys/sync/` primitives +//! (Mutex, Condvar, RwLock, Once, Parker) use when configured for futex mode. + +use crate::sync::atomic::Atomic; +use crate::time::Duration; + +/// An atomic for use as a futex that is at least 32-bits but may be larger. +pub type Futex = Atomic; +/// Must be the underlying type of Futex. +pub type Primitive = u32; + +/// An atomic for use as a futex that is at least 8-bits but may be larger. +pub type SmallFutex = Atomic; +/// Must be the underlying type of SmallFutex. +pub type SmallPrimitive = u32; + +/// Syscall number for futex. +const SYS_FUTEX: u64 = 202; + +/// Futex operations. +const FUTEX_WAIT_PRIVATE: u32 = 0 | 128; +const FUTEX_WAKE_PRIVATE: u32 = 1 | 128; + +/// Timespec matching Linux's `struct timespec`. +#[repr(C)] +struct Timespec { + tv_sec: i64, + tv_nsec: i64, +} + +/// Wait on a futex. +/// +/// Atomically checks `*futex == expected`, and if so, suspends the thread +/// until woken by `futex_wake`, a timeout expires, or a spurious wakeup. +/// +/// Returns `true` if woken normally (or spuriously), `false` on timeout. +pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) -> bool { + let timespec = timeout.and_then(|dur| { + Some(Timespec { tv_sec: dur.as_secs().try_into().ok()?, tv_nsec: dur.subsec_nanos() as i64 }) + }); + + let timeout_ptr = match timespec.as_ref() { + Some(ts) => ts as *const Timespec as u64, + None => 0, + }; + + let ret = unsafe { + vibix_abi::syscall::syscall4( + SYS_FUTEX, + futex as *const Atomic as u64, + FUTEX_WAIT_PRIVATE as u64, + expected as u64, + timeout_ptr, + ) + }; + + // -ETIMEDOUT = -110 + ret != -110 +} + +/// Wake one thread waiting on the futex. +/// +/// Returns `true` if a thread was woken. +#[inline] +pub fn futex_wake(futex: &Atomic) -> bool { + unsafe { + vibix_abi::syscall::syscall3( + SYS_FUTEX, + futex as *const Atomic as u64, + FUTEX_WAKE_PRIVATE as u64, + 1, + ) > 0 + } +} + +/// Wake all threads waiting on the futex. +#[inline] +pub fn futex_wake_all(futex: &Atomic) { + unsafe { + vibix_abi::syscall::syscall3( + SYS_FUTEX, + futex as *const Atomic as u64, + FUTEX_WAKE_PRIVATE as u64, + i32::MAX as u64, + ); + } +} diff --git a/library/std/src/sys/pal/vibix/mod.rs b/library/std/src/sys/pal/vibix/mod.rs index 2b366a67..1619ba56 100644 --- a/library/std/src/sys/pal/vibix/mod.rs +++ b/library/std/src/sys/pal/vibix/mod.rs @@ -8,6 +8,8 @@ use crate::io; +pub mod futex; + pub fn unsupported() -> io::Result { Err(unsupported_err()) } diff --git a/library/std/src/sys/sync/condvar/mod.rs b/library/std/src/sys/sync/condvar/mod.rs index 83cf0ae6..8eda68b2 100644 --- a/library/std/src/sys/sync/condvar/mod.rs +++ b/library/std/src/sys/sync/condvar/mod.rs @@ -8,6 +8,7 @@ cfg_select! { target_os = "dragonfly", target_os = "motor", target_os = "fuchsia", + target_os = "vibix", all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", ) => { diff --git a/library/std/src/sys/sync/mutex/mod.rs b/library/std/src/sys/sync/mutex/mod.rs index e3d6ad11..0f3e2c2e 100644 --- a/library/std/src/sys/sync/mutex/mod.rs +++ b/library/std/src/sys/sync/mutex/mod.rs @@ -7,6 +7,7 @@ cfg_select! { target_os = "openbsd", target_os = "motor", target_os = "dragonfly", + target_os = "vibix", all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", ) => { diff --git a/library/std/src/sys/sync/once/mod.rs b/library/std/src/sys/sync/once/mod.rs index 5796c6d2..2c6b852c 100644 --- a/library/std/src/sys/sync/once/mod.rs +++ b/library/std/src/sys/sync/once/mod.rs @@ -19,6 +19,7 @@ cfg_select! { target_os = "dragonfly", target_os = "fuchsia", target_os = "hermit", + target_os = "vibix", ) => { mod futex; pub use futex::{Once, OnceState}; diff --git a/library/std/src/sys/sync/rwlock/mod.rs b/library/std/src/sys/sync/rwlock/mod.rs index 8603fca2..e4af188b 100644 --- a/library/std/src/sys/sync/rwlock/mod.rs +++ b/library/std/src/sys/sync/rwlock/mod.rs @@ -7,6 +7,7 @@ cfg_select! { target_os = "openbsd", target_os = "dragonfly", target_os = "fuchsia", + target_os = "vibix", all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", target_os = "motor", diff --git a/library/std/src/sys/sync/thread_parking/mod.rs b/library/std/src/sys/sync/thread_parking/mod.rs index 9d5a0a99..ffd31cad 100644 --- a/library/std/src/sys/sync/thread_parking/mod.rs +++ b/library/std/src/sys/sync/thread_parking/mod.rs @@ -10,6 +10,7 @@ cfg_select! { target_os = "fuchsia", target_os = "motor", target_os = "hermit", + target_os = "vibix", ) => { mod futex; pub use futex::Parker; diff --git a/library/std/src/sys/thread/mod.rs b/library/std/src/sys/thread/mod.rs index 9816981c..19c3871a 100644 --- a/library/std/src/sys/thread/mod.rs +++ b/library/std/src/sys/thread/mod.rs @@ -1,4 +1,8 @@ cfg_select! { + target_os = "vibix" => { + mod vibix; + pub use vibix::{Thread, available_parallelism, current_os_id, set_name, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + } target_os = "hermit" => { mod hermit; pub use hermit::{Thread, available_parallelism, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; diff --git a/library/std/src/sys/thread/vibix.rs b/library/std/src/sys/thread/vibix.rs new file mode 100644 index 00000000..bfeb1754 --- /dev/null +++ b/library/std/src/sys/thread/vibix.rs @@ -0,0 +1,297 @@ +//! Thread implementation for vibix. +//! +//! Uses the `clone` syscall with CLONE_VM|CLONE_THREAD|... to create threads, +//! and futex-based join via CLONE_CHILD_CLEARTID. + +use crate::ffi::CStr; +use crate::io; +use crate::num::NonZero; +use crate::sync::atomic::{AtomicU32, Ordering}; +use crate::thread::ThreadInit; +use crate::time::Duration; + +/// Minimum stack size for a vibix thread (1 MiB). +pub const DEFAULT_MIN_STACK_SIZE: usize = 1 << 20; + +/// Clone flags for pthreads-style threading. +const CLONE_VM: u64 = 0x0000_0100; +const CLONE_FS: u64 = 0x0000_0200; +const CLONE_FILES: u64 = 0x0000_0400; +const CLONE_SIGHAND: u64 = 0x0000_0800; +const CLONE_THREAD: u64 = 0x0001_0000; +const CLONE_SYSVSEM: u64 = 0x0004_0000; +const CLONE_SETTLS: u64 = 0x0008_0000; +const CLONE_PARENT_SETTID: u64 = 0x0010_0000; +const CLONE_CHILD_CLEARTID: u64 = 0x0020_0000; + +const CLONE_THREAD_FLAGS: u64 = CLONE_VM + | CLONE_FS + | CLONE_FILES + | CLONE_SIGHAND + | CLONE_THREAD + | CLONE_SETTLS + | CLONE_PARENT_SETTID + | CLONE_CHILD_CLEARTID + | CLONE_SYSVSEM; + +/// Syscall numbers. +const SYS_CLONE: u64 = 56; +const SYS_MMAP: u64 = 9; +const SYS_MUNMAP: u64 = 11; +const SYS_SCHED_YIELD: u64 = 24; +const SYS_NANOSLEEP: u64 = 35; +const SYS_GETTID: u64 = 186; +const SYS_EXIT: u64 = 60; + +/// mmap constants. +const PROT_READ: u64 = 0x1; +const PROT_WRITE: u64 = 0x2; +const MAP_PRIVATE: u64 = 0x02; +const MAP_ANONYMOUS: u64 = 0x20; + +/// Timespec structure matching Linux's `struct timespec`. +#[repr(C)] +struct Timespec { + tv_sec: i64, + tv_nsec: i64, +} + +pub struct Thread { + /// Pointer to the `ThreadData` allocation (used for join and cleanup). + data: *mut ThreadData, +} + +/// Per-thread data allocated on the heap and shared between parent and child. +/// The `child_tid` field is set by the kernel (CLONE_PARENT_SETTID) and +/// cleared atomically + futex-woken on thread exit (CLONE_CHILD_CLEARTID). +#[repr(C)] +struct ThreadData { + /// The child's TID -- set by clone, cleared on exit. + child_tid: AtomicU32, + /// Base of the mmap'd stack allocation. + stack_base: *mut u8, + /// Size of the stack allocation. + stack_size: usize, + /// The thread init data (consumed by the child on first run). + init: Option>, +} + +unsafe impl Send for Thread {} +unsafe impl Sync for Thread {} + +impl Thread { + /// Spawn a new thread. + /// + /// # Safety + /// See `thread::Builder::spawn_unchecked` for safety requirements. + pub unsafe fn new(stack: usize, init: Box) -> io::Result { + // Round stack up to page size (4 KiB). + let stack_size = (stack + 4095) & !4095; + + // Allocate stack via mmap (grows downward on x86_64). + let stack_base = unsafe { + vibix_abi::syscall::syscall6( + SYS_MMAP, + 0, // addr: kernel chooses + stack_size as u64, // length + PROT_READ | PROT_WRITE, // prot + MAP_PRIVATE | MAP_ANONYMOUS, // flags + u64::MAX, // fd: -1 (no file) + 0, // offset + ) + }; + + if stack_base < 0 { + return Err(io::Error::from_raw_os_error(-stack_base as i32)); + } + let stack_base = stack_base as *mut u8; + + // Allocate ThreadData on the heap. + let data = Box::into_raw(Box::new(ThreadData { + child_tid: AtomicU32::new(0), + stack_base, + stack_size, + init: Some(init), + })); + + // Stack grows downward: top = base + size. + // We place the ThreadData pointer at the top of the stack so the + // trampoline can retrieve it. + let stack_top = unsafe { stack_base.add(stack_size) }; + + // Subtract 8 bytes from stack_top to store the data pointer, ensuring + // 16-byte alignment for the entry point (stack_top - 8 is 8-byte aligned, + // and after the implicit "call" alignment, the function entry sees 16-byte + // aligned RSP). + let stack_top = unsafe { stack_top.sub(16) }; + unsafe { + *(stack_top as *mut *mut ThreadData) = data; + } + + // Clone the thread. + // The child starts at `thread_trampoline` with RSP = stack_top. + // We use inline asm to call clone because we need to set up the child + // to jump to our trampoline with the correct stack. + let child_tid_ptr = &(*data).child_tid as *const AtomicU32 as *mut u32; + let ret: i64; + unsafe { + core::arch::asm!( + // syscall: clone(flags, stack, parent_tid, child_tid, tls) + // rax = 56 (SYS_clone) + // rdi = flags + // rsi = stack_top + // rdx = &parent_tid (same as child_tid for us) + // r10 = &child_tid + // r8 = tls (0, we inherit parent TLS for now -- kernel allocates new TLS) + "syscall", + // In parent: rax = child TID (> 0) + // In child: rax = 0 + "test rax, rax", + "jnz 2f", + // --- Child path --- + // RSP is already set to stack_top by the kernel. + // Load the ThreadData pointer from [rsp]. + "mov rdi, [rsp]", + "call {trampoline}", + // trampoline should not return, but just in case: + "mov rdi, 0", + "mov rax, 60", + "syscall", + "2:", + trampoline = sym thread_trampoline, + inlateout("rax") SYS_CLONE as i64 => ret, + in("rdi") CLONE_THREAD_FLAGS, + in("rsi") stack_top, + in("rdx") child_tid_ptr, + inlateout("r10") child_tid_ptr => _, + inlateout("r8") 0u64 => _, + lateout("rcx") _, + lateout("r11") _, + lateout("r9") _, + options(nostack), + ); + } + + if ret < 0 { + // Clone failed -- clean up. + unsafe { + vibix_abi::syscall::syscall2(SYS_MUNMAP, stack_base as u64, stack_size as u64); + drop(Box::from_raw(data)); + } + return Err(io::Error::from_raw_os_error(-ret as i32)); + } + + Ok(Thread { data }) + } + + /// Wait for the thread to exit. + pub fn join(self) { + let data = unsafe { &*self.data }; + + // Futex-wait on child_tid until the kernel clears it to 0. + loop { + let tid = data.child_tid.load(Ordering::Acquire); + if tid == 0 { + break; + } + // FUTEX_WAIT on the child_tid address. + crate::sys::futex::futex_wait( + // The futex functions take &Atomic; AtomicU32 is Atomic. + unsafe { + &*(&data.child_tid as *const AtomicU32 + as *const crate::sync::atomic::Atomic) + }, + tid, + None, + ); + } + + // Thread has exited. Clean up. + let data = unsafe { Box::from_raw(self.data) }; + unsafe { + vibix_abi::syscall::syscall2( + SYS_MUNMAP, + data.stack_base as u64, + data.stack_size as u64, + ); + } + } +} + +/// Trampoline function called by the child thread. +/// +/// # Safety +/// `data_ptr` must be a valid pointer to a `ThreadData` whose `init` field +/// is `Some`. +unsafe extern "C" fn thread_trampoline(data_ptr: *mut ThreadData) -> ! { + let data = unsafe { &mut *data_ptr }; + + // Take the init data. + let init = data.init.take().unwrap(); + let rust_start = init.init(); + rust_start(); + + // Run TLS destructors. + unsafe { + crate::sys::thread_local::destructors::run(); + } + crate::rt::thread_cleanup(); + + // Exit just this thread (not the whole process). + unsafe { + vibix_abi::syscall::syscall1(SYS_EXIT, 0); + } + // Unreachable, but the compiler needs a diverging type. + loop { + core::hint::spin_loop(); + } +} + +/// Return the number of available CPUs. +/// vibix currently supports only 1 CPU. +pub fn available_parallelism() -> io::Result> { + Ok(unsafe { NonZero::new_unchecked(1) }) +} + +/// Get the current thread's OS-level ID (TID). +pub fn current_os_id() -> Option { + let tid = unsafe { vibix_abi::syscall::syscall0(SYS_GETTID as u64) }; + Some(tid as u64) +} + +/// Yield the current thread's timeslice. +#[inline] +pub fn yield_now() { + unsafe { + vibix_abi::syscall::syscall0(SYS_SCHED_YIELD); + } +} + +/// Set the current thread's name (stub -- not yet supported). +pub fn set_name(_name: &CStr) { + // No-op: vibix does not yet support thread naming. +} + +/// Sleep for the specified duration. +pub fn sleep(dur: Duration) { + let mut req = Timespec { + tv_sec: dur.as_secs() as i64, + tv_nsec: dur.subsec_nanos() as i64, + }; + + // Loop in case of EINTR. + loop { + let ret = unsafe { + vibix_abi::syscall::syscall2( + SYS_NANOSLEEP, + &req as *const Timespec as u64, + &mut req as *mut Timespec as u64, + ) + }; + if ret == 0 || ret != -4 { + // 0 = success, anything other than -EINTR = done + break; + } + // -EINTR: remaining time is in req, loop again. + } +} diff --git a/userspace/vibix_abi/src/futex.rs b/userspace/vibix_abi/src/futex.rs new file mode 100644 index 00000000..b5786392 --- /dev/null +++ b/userspace/vibix_abi/src/futex.rs @@ -0,0 +1,62 @@ +//! Futex syscall wrappers for vibix. +//! +//! Provides FUTEX_WAIT and FUTEX_WAKE operations matching the Linux interface. + +use core::sync::atomic::AtomicU32; + +use crate::syscall; +use crate::thread::Timespec; + +/// Syscall number for futex. +const SYS_FUTEX: u64 = 202; + +/// Futex operations. +const FUTEX_WAIT: u32 = 0; +const FUTEX_WAKE: u32 = 1; +const FUTEX_PRIVATE_FLAG: u32 = 128; +const FUTEX_WAIT_PRIVATE: u32 = FUTEX_WAIT | FUTEX_PRIVATE_FLAG; +const FUTEX_WAKE_PRIVATE: u32 = FUTEX_WAKE | FUTEX_PRIVATE_FLAG; + +/// Perform a futex wait operation. +/// +/// Atomically checks that `*futex == expected` and suspends the calling thread. +/// Returns when woken by `futex_wake`, on timeout, or spuriously. +/// +/// - `futex`: the futex word to wait on +/// - `expected`: the expected value +/// - `timeout`: optional relative timeout +/// +/// Returns `true` if woken normally, `false` on timeout. +#[inline] +pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<&Timespec>) -> bool { + let timeout_ptr = match timeout { + Some(ts) => ts as *const Timespec as u64, + None => 0, + }; + let ret = unsafe { + syscall::syscall4( + SYS_FUTEX, + futex as *const AtomicU32 as u64, + FUTEX_WAIT_PRIVATE as u64, + expected as u64, + timeout_ptr, + ) + }; + // -ETIMEDOUT = -110 + ret != -110 +} + +/// Wake up to `count` threads waiting on the futex. +/// +/// Returns the number of threads woken. +#[inline] +pub fn futex_wake(futex: &AtomicU32, count: u32) -> i64 { + unsafe { + syscall::syscall3( + SYS_FUTEX, + futex as *const AtomicU32 as u64, + FUTEX_WAKE_PRIVATE as u64, + count as u64, + ) + } +} diff --git a/userspace/vibix_abi/src/lib.rs b/userspace/vibix_abi/src/lib.rs index 88888e66..3a046ecd 100644 --- a/userspace/vibix_abi/src/lib.rs +++ b/userspace/vibix_abi/src/lib.rs @@ -10,6 +10,8 @@ pub mod alloc; pub mod errno; pub mod fs; +pub mod futex; pub mod process; pub mod stdio; pub mod syscall; +pub mod thread; diff --git a/userspace/vibix_abi/src/thread.rs b/userspace/vibix_abi/src/thread.rs new file mode 100644 index 00000000..95061614 --- /dev/null +++ b/userspace/vibix_abi/src/thread.rs @@ -0,0 +1,127 @@ +//! Thread lifecycle wrappers for vibix. +//! +//! Provides `clone`-based thread creation and related syscalls. + +use core::sync::atomic::{AtomicU32, Ordering}; + +use crate::syscall; + +/// Syscall numbers (Linux x86_64 ABI). +const SYS_SCHED_YIELD: u64 = 24; +const SYS_NANOSLEEP: u64 = 35; +const SYS_CLONE: u64 = 56; +const SYS_GETTID: u64 = 186; +const SYS_SET_TID_ADDRESS: u64 = 218; + +/// Clone flags for pthreads-style threading. +pub const CLONE_VM: u64 = 0x0000_0100; +pub const CLONE_FS: u64 = 0x0000_0200; +pub const CLONE_FILES: u64 = 0x0000_0400; +pub const CLONE_SIGHAND: u64 = 0x0000_0800; +pub const CLONE_THREAD: u64 = 0x0001_0000; +pub const CLONE_SYSVSEM: u64 = 0x0004_0000; +pub const CLONE_SETTLS: u64 = 0x0008_0000; +pub const CLONE_PARENT_SETTID: u64 = 0x0010_0000; +pub const CLONE_CHILD_CLEARTID: u64 = 0x0020_0000; + +/// The standard set of clone flags for creating a new thread. +pub const CLONE_THREAD_FLAGS: u64 = CLONE_VM + | CLONE_FS + | CLONE_FILES + | CLONE_SIGHAND + | CLONE_THREAD + | CLONE_SETTLS + | CLONE_PARENT_SETTID + | CLONE_CHILD_CLEARTID + | CLONE_SYSVSEM; + +/// Timespec structure matching Linux's `struct timespec`. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Timespec { + pub tv_sec: i64, + pub tv_nsec: i64, +} + +/// Create a new thread via the `clone` syscall. +/// +/// - `flags`: combination of CLONE_* constants +/// - `stack_top`: pointer to the top of the child's stack +/// - `parent_tid`: written with the child's TID (CLONE_PARENT_SETTID) +/// - `child_tid`: address set for CLONE_CHILD_CLEARTID (cleared on exit) +/// - `tls`: TLS base for the new thread (CLONE_SETTLS) +/// +/// On success in the parent, returns the child TID (> 0). +/// On success in the child, returns 0. +/// On failure, returns a negative errno. +/// +/// # Safety +/// +/// The caller must ensure stack_top is a valid stack pointer and that +/// the entry function is properly set up (e.g., via assembly trampoline). +#[inline] +pub unsafe fn clone( + flags: u64, + stack_top: *mut u8, + parent_tid: *mut u32, + child_tid: *mut u32, + tls: u64, +) -> i64 { + // clone(flags, stack, parent_tid, child_tid, tls) + // Note: Linux x86_64 clone ABI puts child_tid in r10 and tls in r8, + // matching syscall4/5 register assignment. + unsafe { + syscall::syscall5( + SYS_CLONE, + flags, + stack_top as u64, + parent_tid as u64, + child_tid as u64, + tls, + ) + } +} + +/// Yield the current thread's timeslice. +#[inline] +pub fn sched_yield() { + unsafe { + syscall::syscall0(SYS_SCHED_YIELD); + } +} + +/// Sleep for the specified duration. +/// +/// Returns 0 on success, or a negative errno (e.g., -EINTR) if interrupted. +#[inline] +pub fn nanosleep(req: &Timespec) -> i64 { + unsafe { syscall::syscall2(SYS_NANOSLEEP, req as *const Timespec as u64, 0) } +} + +/// Get the current thread's TID. +#[inline] +pub fn gettid() -> i64 { + unsafe { syscall::syscall0(SYS_GETTID) } +} + +/// Set the clear_child_tid address for the calling thread. +#[inline] +pub fn set_tid_address(tidptr: *mut u32) -> i64 { + unsafe { syscall::syscall1(SYS_SET_TID_ADDRESS, tidptr as u64) } +} + +/// Wait for a thread to exit by futex-waiting on its `clear_child_tid` word. +/// +/// The kernel atomically sets `*tid_addr = 0` and does `FUTEX_WAKE` on the +/// address when the thread exits (if CLONE_CHILD_CLEARTID was set). +#[inline] +pub fn join_thread(tid_addr: &AtomicU32) { + loop { + let val = tid_addr.load(Ordering::Acquire); + if val == 0 { + return; + } + // futex_wait on the tid address until it becomes 0 + crate::futex::futex_wait(tid_addr, val, None); + } +} From ae9295b12344b1ab3b35f7c11544fed1efeb3c17 Mon Sep 17 00:00:00 2001 From: vibix auto-engineer Date: Tue, 5 May 2026 06:58:09 +0000 Subject: [PATCH 2/2] Fix futex timeout overflow and clean up AtomicU32 cast - Clamp oversized durations to i64::MAX instead of silently converting to an unbounded wait (the try_into().ok()? path dropped to None) - Use Atomic directly in ThreadData to avoid the fragile pointer cast between AtomicU32 and Atomic in join() Co-Authored-By: Claude Opus 4.6 --- library/std/src/sys/pal/vibix/futex.rs | 6 ++++-- library/std/src/sys/thread/vibix.rs | 18 +++++------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/library/std/src/sys/pal/vibix/futex.rs b/library/std/src/sys/pal/vibix/futex.rs index 866b170a..1b50f8ff 100644 --- a/library/std/src/sys/pal/vibix/futex.rs +++ b/library/std/src/sys/pal/vibix/futex.rs @@ -37,8 +37,10 @@ struct Timespec { /// /// Returns `true` if woken normally (or spuriously), `false` on timeout. pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) -> bool { - let timespec = timeout.and_then(|dur| { - Some(Timespec { tv_sec: dur.as_secs().try_into().ok()?, tv_nsec: dur.subsec_nanos() as i64 }) + let timespec = timeout.map(|dur| { + let tv_sec = i64::try_from(dur.as_secs()).unwrap_or(i64::MAX); + let tv_nsec = i64::from(dur.subsec_nanos()); + Timespec { tv_sec, tv_nsec } }); let timeout_ptr = match timespec.as_ref() { diff --git a/library/std/src/sys/thread/vibix.rs b/library/std/src/sys/thread/vibix.rs index bfeb1754..8b636dae 100644 --- a/library/std/src/sys/thread/vibix.rs +++ b/library/std/src/sys/thread/vibix.rs @@ -6,7 +6,7 @@ use crate::ffi::CStr; use crate::io; use crate::num::NonZero; -use crate::sync::atomic::{AtomicU32, Ordering}; +use crate::sync::atomic::{Atomic, Ordering}; use crate::thread::ThreadInit; use crate::time::Duration; @@ -67,7 +67,7 @@ pub struct Thread { #[repr(C)] struct ThreadData { /// The child's TID -- set by clone, cleared on exit. - child_tid: AtomicU32, + child_tid: Atomic, /// Base of the mmap'd stack allocation. stack_base: *mut u8, /// Size of the stack allocation. @@ -108,7 +108,7 @@ impl Thread { // Allocate ThreadData on the heap. let data = Box::into_raw(Box::new(ThreadData { - child_tid: AtomicU32::new(0), + child_tid: Atomic::new(0), stack_base, stack_size, init: Some(init), @@ -132,7 +132,7 @@ impl Thread { // The child starts at `thread_trampoline` with RSP = stack_top. // We use inline asm to call clone because we need to set up the child // to jump to our trampoline with the correct stack. - let child_tid_ptr = &(*data).child_tid as *const AtomicU32 as *mut u32; + let child_tid_ptr = &(*data).child_tid as *const Atomic as *mut u32; let ret: i64; unsafe { core::arch::asm!( @@ -195,15 +195,7 @@ impl Thread { break; } // FUTEX_WAIT on the child_tid address. - crate::sys::futex::futex_wait( - // The futex functions take &Atomic; AtomicU32 is Atomic. - unsafe { - &*(&data.child_tid as *const AtomicU32 - as *const crate::sync::atomic::Atomic) - }, - tid, - None, - ); + crate::sys::futex::futex_wait(&data.child_tid, tid, None); } // Thread has exited. Clean up.