diff --git a/library/std/build.rs b/library/std/build.rs index 36516978b7a09..7630cb3a4eff1 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -1,6 +1,8 @@ use std::env; fn main() { + let is_custom_os = || env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("custom"); + println!("cargo:rerun-if-changed=build.rs"); let target = env::var("TARGET").expect("TARGET was not set"); if target.contains("freebsd") { @@ -40,6 +42,7 @@ fn main() { || target.contains("xous") || target.contains("hurd") || target.contains("uefi") + || is_custom_os() // See src/bootstrap/synthetic_targets.rs || env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok() { diff --git a/library/std/src/env.rs b/library/std/src/env.rs index f67f6034d3412..404c16e5abe78 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -926,6 +926,7 @@ pub mod consts { /// - dragonfly /// - netbsd /// - openbsd + /// - custom /// - solaris /// - android /// - windows diff --git a/library/std/src/os/custom/mod.rs b/library/std/src/os/custom/mod.rs new file mode 100644 index 0000000000000..57070088b66a5 --- /dev/null +++ b/library/std/src/os/custom/mod.rs @@ -0,0 +1,310 @@ +//! Platform-specific parts of the standard library +//! that can be plugged-in at runtime. +//! +//! Using these modules, code can set an implementation for +//! each platform-specific part of the standard library at +//! runtime. It does so via the `set_impl` functions. +//! +//! This is primarily geared toward experimental platforms such +//! as new kernels and bare-bones environments, where you might +//! want to use the standard library without recompiling +//! everything or adding support upstream. +//! +//! # Initial state +//! +//! Initially, as no implementation has been defined, most +//! of these parts panic with this message: +//! +//! ```text +//! std::os::thread::IMPL has not been initialized at this point. +//! ``` +//! +//! There are three exceptions: +//! +//! - There is a default allocator, allowing for 16KiB of heap use. +//! It implements the sequential fit algorithm. +//! - Initially, locking primitives are functional and behave as spinlocks. +//! - Before `os::set_impl` has been called, any call to `abort_internal` +//! will result in an infinite loop. +//! - Before `thread::set_impl` has been called, `None` is used where +//! `current_thread_id` would be called. +//! +//! These should be set/changed as soon as possible, as they are used internally. +//! +//! # To do +//! +//! - thread parking support +//! - thread-safe Once support +//! - invocation arguments (returns an empty iterator at the moment) +//! - `pipe::read2` +//! + +#![unstable(issue = "none", feature = "std_internals")] + +#[doc(hidden)] +#[macro_export] +macro_rules! custom_os_impl { + ($module:ident, $method:ident $(, $arg:expr)*) => {{ + let errmsg = concat!( + "std::os::custom::", stringify!($module), "::IMPL", + " has not been initialized at this point", + ); + + let rwlock = &crate::os::custom::$module::IMPL; + let reader = rwlock.read().expect("poisoned lock"); + let some_impl = reader.as_ref().expect(errmsg); + + some_impl.$method($($arg,)*) + }}; +} + +macro_rules! static_rwlock_box_impl { + ($api:ident) => { + pub(crate) static IMPL: RwLock>> = RwLock::new(None); + + /// Sets the implementation singleton + /// + /// This parameter takes a `transition` closure responsible + /// for properly transitioning from the previous implementation + /// to a new one. + /// + /// Initially, there is no implementation; the first time this is called, + /// the closure parameter will be `None`. + /// + /// Removing an implementation (i.e. setting the internal singleton to `None`) + /// is intentionally not allowed. + pub fn set_impl>) -> Box>(transition: F) { + let mut writer = IMPL.write().expect("poisoned lock"); + let maybe_impl = core::mem::replace(&mut *writer, None); + let new_impl = transition(maybe_impl); + *writer = Some(new_impl); + } + }; +} + +/// Platform-specific allocator +pub mod alloc { + use crate::alloc::GlobalAlloc; + use crate::sync::RwLock; + + static_rwlock_box_impl!(Allocator); + + /// Platform-specific allocator + pub trait Allocator: GlobalAlloc + Send + Sync {} +} + +/// Platform-specific interface to a filesystem +pub mod fs { + use crate::io; + use crate::path::{Path, PathBuf}; + use crate::sync::RwLock; + + #[doc(inline)] + pub use crate::sys::fs::{ + DirEntry, File, FileApi, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, + ReadDir, ReadDirApi, + }; + + static_rwlock_box_impl!(FilesystemInterface); + + /// Platform-specific interface to a filesystem + pub trait FilesystemInterface: Send + Sync { + fn open(&self, path: &Path, opts: &OpenOptions) -> io::Result; + fn mkdir(&self, p: &Path) -> io::Result<()>; + fn read_dir(&self, p: &Path) -> io::Result; + fn unlink(&self, p: &Path) -> io::Result<()>; + fn rename(&self, old: &Path, new: &Path) -> io::Result<()>; + fn set_perm(&self, p: &Path, perm: FilePermissions) -> io::Result<()>; + fn rmdir(&self, p: &Path) -> io::Result<()>; + fn remove_dir_all(&self, path: &Path) -> io::Result<()>; + fn try_exists(&self, path: &Path) -> io::Result; + fn readlink(&self, p: &Path) -> io::Result; + fn symlink(&self, original: &Path, link: &Path) -> io::Result<()>; + fn link(&self, src: &Path, dst: &Path) -> io::Result<()>; + fn stat(&self, p: &Path) -> io::Result; + fn lstat(&self, p: &Path) -> io::Result; + fn canonicalize(&self, p: &Path) -> io::Result; + fn copy(&self, from: &Path, to: &Path) -> io::Result; + } +} + +/// Platform-specific management of `AtomicU32`-based futexes +pub mod futex { + use crate::sync::{atomic::AtomicU32, RwLock}; + use crate::time::Duration; + + // by default (None) => spinlock + static_rwlock_box_impl!(FutexManager); + + /// Platform-specific management of `AtomicU32`-based futexes + pub trait FutexManager: Send + Sync { + /// Wait for a futex_wake operation to wake us. + /// + /// Returns directly if the futex doesn't hold the expected value. + /// + /// Returns false on timeout, and true in all other cases. + fn futex_wait(&self, futex: &AtomicU32, expected: u32, timeout: Option) -> bool; + + /// Wake up one thread that's blocked on futex_wait on this futex. + /// + /// Returns true if this actually woke up such a thread, + /// or false if no thread was waiting on this futex. + /// + /// On some platforms, this always returns false. + fn futex_wake(&self, futex: &AtomicU32) -> bool; + + /// Wake up all threads that are waiting on futex_wait on this futex. + fn futex_wake_all(&self, futex: &AtomicU32); + } +} + +/// Platform-specific interface to a network +pub mod net { + use crate::io; + use crate::net::SocketAddr; + use crate::sync::RwLock; + use crate::time::Duration; + + #[doc(inline)] + pub use crate::sys::net::{ + LookupHost, TcpListener, TcpListenerApi, TcpStream, TcpStreamApi, UdpSocket, UdpSocketApi, + }; + + static_rwlock_box_impl!(NetworkInterface); + + /// Platform-specific interface to a network + pub trait NetworkInterface: Send + Sync { + fn tcp_connect( + &self, + addr: &SocketAddr, + timeout: Option, + ) -> io::Result; + fn tcp_bind(&self, addr: &SocketAddr) -> io::Result; + fn udp_bind(&self, addr: &SocketAddr) -> io::Result; + fn lookup_str(&self, v: &str) -> io::Result; + fn lookup_tuple(&self, v: (&str, u16)) -> io::Result; + } +} + +/// Platform-specific interface to the running operating system +pub mod os { + use crate::ffi::{OsStr, OsString}; + use crate::io; + use crate::path::{Path, PathBuf}; + use crate::sync::RwLock; + + #[doc(inline)] + pub use crate::sys::os::{Env, JoinPathsError, SplitPaths, Variable}; + + static_rwlock_box_impl!(Os); + + /// Platform-specific interface to the running operating system + pub trait Os: Send + Sync { + fn errno(&self) -> i32; + fn error_string(&self, errno: i32) -> String; + + fn current_exe(&self) -> io::Result; + fn env(&self) -> Env; + fn get_env(&self, variable: &OsStr) -> Option; + fn set_env(&self, variable: &OsStr, value: &OsStr) -> io::Result<()>; + fn unset_env(&self, variable: &OsStr) -> io::Result<()>; + fn env_path_delim(&self) -> &'static str; + + fn getcwd(&self) -> io::Result; + fn temp_dir(&self) -> PathBuf; + fn home_dir(&self) -> Option; + fn chdir(&self, path: &Path) -> io::Result<()>; + + fn exit(&self, code: i32) -> !; + fn get_pid(&self) -> u32; + + fn decode_error_kind(&self, errno: i32) -> crate::io::ErrorKind; + fn is_interrupted(&self, errno: i32) -> bool; + fn hashmap_random_keys(&self) -> (u64, u64); + } +} + +/// Platform-specific management of processes +pub mod process { + use crate::io; + use crate::sync::RwLock; + + #[doc(inline)] + pub use crate::sys_common::process::{CommandEnv, CommandEnvs}; + + #[doc(inline)] + pub use crate::sys::process::{Command, ExitStatus, Process, ProcessApi, Stdio, StdioPipes}; + + static_rwlock_box_impl!(ProcessManager); + + /// Platform-specific management of processes + pub trait ProcessManager: Send + Sync { + fn spawn(&self, command: &Command) -> io::Result<(Process, StdioPipes)>; + } +} + +/// Platform-specific standard IO interface +pub mod stdio { + use crate::io; + use crate::sync::RwLock; + + static_rwlock_box_impl!(StdioInterface); + + /// Platform-specific standard IO interface + pub trait StdioInterface: Send + Sync { + fn read_stdin(&self, buf: &mut [u8]) -> io::Result; + fn write_stdout(&self, buf: &[u8]) -> io::Result; + fn flush_stdout(&self) -> io::Result<()>; + fn write_stderr(&self, buf: &[u8]) -> io::Result; + fn flush_stderr(&self) -> io::Result<()>; + fn is_ebadf(&self, err: &io::Error) -> bool; + fn panic_output(&self) -> Option>; + } +} + +/// Platform-specific management of threads +pub mod thread { + use crate::ffi::CStr; + use crate::io; + use crate::num::NonZeroUsize; + use crate::sync::RwLock; + use crate::time::Duration; + + #[doc(inline)] + pub use crate::sys::thread::{Thread, ThreadApi}; + + static_rwlock_box_impl!(ThreadManager); + + pub type ThreadId = usize; + + /// Platform-specific management of threads + pub trait ThreadManager: Send + Sync { + /// unsafe: see thread::Builder::spawn_unchecked for safety requirements + unsafe fn new(&self, stack: usize, p: Box) -> io::Result; + fn yield_now(&self); + fn set_name(&self, name: &CStr); + fn sleep(&self, dur: Duration); + fn join(&self, thread: &Thread); + fn available_parallelism(&self) -> io::Result; + + /// Must return None for the initial thread + fn current_thread_id(&self) -> Option; + + // todo: thread parking + } +} + +/// Platform-specific timer interface +pub mod time { + use crate::sync::RwLock; + + pub use crate::sys::time::{Instant, SystemTime}; + + static_rwlock_box_impl!(Timer); + + /// Platform-specific timer interface + pub trait Timer: Send + Sync { + fn now_instant(&self) -> Instant; + fn now_systime(&self) -> SystemTime; + } +} diff --git a/library/std/src/os/fake_unix_windows.rs b/library/std/src/os/fake_unix_windows.rs new file mode 100644 index 0000000000000..207b22418b334 --- /dev/null +++ b/library/std/src/os/fake_unix_windows.rs @@ -0,0 +1,42 @@ +//! This is a doc-build trick artefact. Check the upstream documentation. + +/// This is a doc-build trick artefact. Check the upstream documentation. +pub mod process { + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod ExitStatusExt {} +} + +/// This is a doc-build trick artefact. Check the upstream documentation. +pub mod fs { + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod PermissionsExt {} + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod symlink {} + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod symlink_file {} + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod symlink_dir {} +} + +/// This is a doc-build trick artefact. Check the upstream documentation. +pub mod ffi { + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod OsStrExt { + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod encode_wide {} + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod from_bytes {} + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod as_bytes {} + } + + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod OsStringExt { + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod from_wide {} + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod from_vec {} + /// This is a doc-build trick artefact. Check the upstream documentation. + pub mod into_vec {} + } +} diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs index 11ad21515fdec..c33eac72183a3 100644 --- a/library/std/src/os/mod.rs +++ b/library/std/src/os/mod.rs @@ -18,44 +18,53 @@ pub mod raw; #[cfg(all( doc, any( + target_os = "custom", all(target_arch = "wasm32", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) ))] #[unstable(issue = "none", feature = "std_internals")] -pub mod unix {} +#[path = "fake_unix_windows.rs"] +pub mod linux; #[cfg(all( doc, any( + target_os = "custom", all(target_arch = "wasm32", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) ))] #[unstable(issue = "none", feature = "std_internals")] -pub mod linux {} +#[path = "fake_unix_windows.rs"] +pub mod unix; #[cfg(all( doc, any( + target_os = "custom", all(target_arch = "wasm32", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) ))] #[unstable(issue = "none", feature = "std_internals")] -pub mod wasi {} +#[path = "fake_unix_windows.rs"] +pub mod wasi; #[cfg(all( doc, any( + target_os = "custom", all(target_arch = "wasm32", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) ))] #[unstable(issue = "none", feature = "std_internals")] -pub mod windows {} +#[path = "fake_unix_windows.rs"] +pub mod windows; // unix #[cfg(not(all( doc, any( + target_os = "custom", all(target_arch = "wasm32", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) @@ -67,6 +76,7 @@ pub mod unix; #[cfg(not(all( doc, any( + target_os = "custom", all(target_arch = "wasm32", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) @@ -78,6 +88,7 @@ pub mod linux; #[cfg(not(all( doc, any( + target_os = "custom", all(target_arch = "wasm32", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) @@ -89,6 +100,7 @@ pub mod wasi; #[cfg(not(all( doc, any( + target_os = "custom", all(target_arch = "wasm32", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) @@ -99,6 +111,8 @@ pub mod windows; // Others. #[cfg(target_os = "android")] pub mod android; +#[cfg(target_os = "custom")] +pub mod custom; #[cfg(target_os = "dragonfly")] pub mod dragonfly; #[cfg(target_os = "emscripten")] @@ -153,8 +167,8 @@ pub(crate) mod watchos; #[cfg(target_os = "xous")] pub mod xous; -#[cfg(any(unix, target_os = "wasi", doc))] +#[cfg(all(any(unix, target_os = "wasi", doc), not(target_os = "custom")))] pub mod fd; -#[cfg(any(target_os = "linux", target_os = "android", doc))] +#[cfg(all(any(target_os = "linux", target_os = "android", doc), not(target_os = "custom")))] mod net; diff --git a/library/std/src/sync/mutex.rs b/library/std/src/sync/mutex.rs index b4ae6b7e07ebc..79f3555a6fa44 100644 --- a/library/std/src/sync/mutex.rs +++ b/library/std/src/sync/mutex.rs @@ -275,6 +275,21 @@ impl Mutex { } } + /// On the "custom" platform, allocator and thread local storage + /// implementations use Mutex & RwLock, sometimes while a panic is + /// being handled. + /// + /// The poison checks that these primitives do are deadlock sources + /// in these cases; to prevent these deadlocks, it is preferable to + /// bypass the poison checks. + #[cfg(target_os = "custom")] + #[stable(feature = "rust1", since = "1.0.0")] + pub(crate) fn lock_no_poison_check(&self) -> MutexGuard<'_, T> { + self.inner.lock(); + let poison = poison::Guard::no_check(); + MutexGuard { lock: self, poison } + } + /// Attempts to acquire this lock. /// /// If the lock could not be acquired at this time, then [`Err`] is returned. diff --git a/library/std/src/sync/poison.rs b/library/std/src/sync/poison.rs index 741312d5537e9..aa3315b140996 100644 --- a/library/std/src/sync/poison.rs +++ b/library/std/src/sync/poison.rs @@ -59,6 +59,21 @@ pub struct Guard { panicking: bool, } +#[cfg(target_os = "custom")] +impl Guard { + /// On the "custom" platform, allocator and thread local storage + /// implementations use Mutex & RwLock, sometimes while a panic is + /// being handled. + /// + /// The poison checks that these primitives do are deadlock sources + /// in these cases; to prevent these deadlocks, it is preferable to + /// bypass the poison checks. + pub fn no_check() -> Self { + // by setting `panicking` to true, `poison::Flag::done` doesn't call `thread::panicking` + Self { panicking: true } + } +} + /// A type of error which can be returned whenever a lock is acquired. /// /// Both [`Mutex`]es and [`RwLock`]s are poisoned whenever a thread fails while the lock diff --git a/library/std/src/sync/rwlock.rs b/library/std/src/sync/rwlock.rs index 26aaa2414c979..10db41b516af5 100644 --- a/library/std/src/sync/rwlock.rs +++ b/library/std/src/sync/rwlock.rs @@ -212,6 +212,26 @@ impl RwLock { } } + /// On the "custom" platform, allocator and thread local storage + /// implementations use Mutex & RwLock, sometimes while a panic is + /// being handled. + /// + /// The poison checks that these primitives do are deadlock sources + /// in these cases; to prevent these deadlocks, it is preferable to + /// bypass the poison checks. + #[inline] + #[cfg(target_os = "custom")] + #[stable(feature = "rust1", since = "1.0.0")] + pub(crate) fn read_no_poison_check(&self) -> RwLockReadGuard<'_, T> { + unsafe { + self.inner.read(); + RwLockReadGuard { + data: NonNull::new_unchecked(self.data.get()), + inner_lock: &self.inner, + } + } + } + /// Attempts to acquire this `RwLock` with shared read access. /// /// If the access could not be granted at this time, then `Err` is returned. @@ -300,6 +320,22 @@ impl RwLock { } } + /// On the "custom" platform, allocator and thread local storage + /// implementations use Mutex & RwLock, sometimes while a panic is + /// being handled. + /// + /// The poison checks that these primitives do are deadlock sources + /// in these cases; to prevent these deadlocks, it is preferable to + /// bypass the poison checks. + #[inline] + #[cfg(target_os = "custom")] + #[stable(feature = "rust1", since = "1.0.0")] + pub(crate) fn write_no_poison_check(&self) -> RwLockWriteGuard<'_, T> { + self.inner.write(); + let poison = poison::Guard::no_check(); + RwLockWriteGuard { lock: self, poison } + } + /// Attempts to lock this `RwLock` with exclusive write access. /// /// If the lock could not be acquired at this time, then `Err` is returned. diff --git a/library/std/src/sys/common/thread_local/os_local.rs b/library/std/src/sys/common/thread_local/os_local.rs index 7cf291921228b..40f13e4182cfd 100644 --- a/library/std/src/sys/common/thread_local/os_local.rs +++ b/library/std/src/sys/common/thread_local/os_local.rs @@ -11,7 +11,7 @@ use crate::{fmt, marker, panic, ptr}; pub macro thread_local_inner { // used to generate the `LocalKey` value for const-initialized thread locals (@key $t:ty, const $init:expr) => {{ - #[inline] + #[cfg_attr(not(target_os = "custom"), inline)] #[deny(unsafe_op_in_unsafe_fn)] unsafe fn __getit( _init: $crate::option::Option<&mut $crate::option::Option<$t>>, @@ -20,7 +20,7 @@ pub macro thread_local_inner { // On platforms without `#[thread_local]` we fall back to the // same implementation as below for os thread locals. - #[inline] + #[cfg_attr(not(target_os = "custom"), inline)] const fn __init() -> $t { INIT_EXPR } static __KEY: $crate::thread::local_impl::Key<$t> = $crate::thread::local_impl::Key::new(); @@ -46,12 +46,12 @@ pub macro thread_local_inner { // used to generate the `LocalKey` value for `thread_local!` (@key $t:ty, $init:expr) => { { - #[inline] + #[cfg_attr(not(target_os = "custom"), inline)] fn __init() -> $t { $init } // `#[inline] does not work on windows-gnu due to linking errors around dllimports. // See https://github.com/rust-lang/rust/issues/109797. - #[cfg_attr(not(windows), inline)] + #[cfg_attr(all(not(windows), not(target_os = "custom")), inline)] unsafe fn __getit( init: $crate::option::Option<&mut $crate::option::Option<$t>>, ) -> $crate::option::Option<&'static $t> { diff --git a/library/std/src/sys/custom/alloc.rs b/library/std/src/sys/custom/alloc.rs new file mode 100644 index 0000000000000..35a7c21d9cbad --- /dev/null +++ b/library/std/src/sys/custom/alloc.rs @@ -0,0 +1,278 @@ +#![unstable(issue = "none", feature = "std_internals")] + +use crate::alloc::{GlobalAlloc, Layout, System}; +use crate::os::custom::alloc::IMPL; +use crate::sync::Mutex; +use core::ops::{Deref, DerefMut}; + +// Simple implementation of a sequential fit allocator +// +// minimal size and alignment: two bytes (less than that => wasted bytes) +// [ NN NN ]: uneven, two-byte pointer to next free slot +// [ SS SS ]: even, two-byte size of current spot +// uneven size => size = 2, current pair is the next ptr +// even value of zero => continue to next byte pair +// even value, non-zero => offset into the byte array to next free slot (right-shifted by one bit) + +// ::::: :::::::::::: ::::: ::::: :::::::::::::::::::::::: +// [ XX XX SS SS 00 00 00 00 NN NN XX XX XX XX SS SS NN NN XX XX XX XX NN NN XX XX XX XX XX XX XX XX SS SS NN NN ] +// | \ \ \______ --> _____/___/_________ --> ______________/ +// first free slot \ \_________ <-- ___________/___/ +// \___________ --> ______________________/ +// +// ::::: = occupied slot + +// ALLOCATING +// +// a. keep track of the origin of the current spot pointer (first free slot global var / end of the previous spot) +// b. find first free slot offset (from a global variable) +// c. read the size of the current slot +// d. if the size is enough for the required layout (taking alignment into account), use this spot (goto step f) +// e. else, read next pointer (last pair of the spot) and retry at step c with new offset +// f. if there is unusable space at the beginning of the spot (alignment/padding), create a new spot there +// g. if there is leftover space at the end of the spot, create a new spot there +// h. update the current spot pointer origin so that it points to the next free spot + +// DEALLOCATING +// +// a. find first free slot offset (from a global variable) +// b. arrange the bytes in the freed slot (size pair + next pointer pair) +// note: copy the value of the global "first free slot" variable into the next pointer pair +// c. update the global "first free slot" variable + +// maximum: 0xffff +// more than 0xffff => will cause infinite loops +const SIZE_BYTES: usize = 4096 * 4; +type HeapArray = [u8; SIZE_BYTES]; + +// align the heap to a page +#[repr(align(4096))] +struct Heap(HeapArray); + +impl Deref for Heap { + type Target = HeapArray; + fn deref(&self) -> &HeapArray { + &self.0 + } +} + +impl DerefMut for Heap { + fn deref_mut(&mut self) -> &mut HeapArray { + &mut self.0 + } +} + +static mut HEAP: Heap = Heap(init_heap()); +static FIRST_SLOT: Mutex = Mutex::new(0); + +const fn init_heap() -> [u8; SIZE_BYTES] { + let mut pages = [0; SIZE_BYTES]; + + let len = SIZE_BYTES as u16; + let [a, b] = len.to_ne_bytes(); + pages[0] = a; + pages[1] = b; + + let j = SIZE_BYTES - 2; + let out_of_bounds = 0xffffu16; + let [a, b] = out_of_bounds.to_ne_bytes(); + pages[j + 0] = a; + pages[j + 1] = b; + + pages +} + +struct DefaultAlloc; + +fn read_u16(i: usize) -> usize { + let bytes = unsafe { [HEAP[i + 0], HEAP[i + 1]] }; + u16::from_ne_bytes(bytes) as usize +} + +fn write_u16(i: usize, value: usize) { + let bytes = (value as u16).to_ne_bytes(); + unsafe { HEAP[i..][..2].copy_from_slice(&bytes) } +} + +fn decode_slot(i: usize) -> (usize, usize) { + let first_pair = read_u16(i); + let mut len = first_pair; + let next; + + if (first_pair & 1) != 0 { + // uneven => this is the next pointer (two-byte spot) + next = first_pair; + len = 2; + } else { + next = read_u16(i + len - 2); + } + + (len, next & !1) +} + +fn encode_slot(i: usize, len: usize, next: usize) { + assert_ne!(len, 0); + assert_eq!(len & 1, 0); + + if len != 2 { + write_u16(i, len) + } + + let j = i + len - 2; + write_u16(j, next | 1) +} + +fn free(i: usize, len: usize) { + assert!(len >= 2); + + let mut first_slot = FIRST_SLOT.lock_no_poison_check(); + encode_slot(i, len, *first_slot); + *first_slot = i; +} + +fn alignment_filler(i: usize, req_align: usize) -> usize { + let offset = i & (req_align - 1); + match offset == 0 { + true => 0, + false => req_align - offset, + } +} + +fn prepare_layout(layout: Layout) -> (usize, usize) { + let req_align = layout.align().max(2); // any power of two: 1, 2, 4, 8, 16, 32, 64 + let mut req_size = layout.size(); + + assert_ne!(req_size, 0); + assert_ne!(req_align, 0); + + // make size even + req_size += req_size & 1; + + (req_align, req_size) +} + +fn default_alloc_find(ptr: *mut u8) -> Option { + let start = unsafe { HEAP.as_ptr() } as usize; + let ptr = ptr as usize; + + ptr.checked_sub(start).filter(|offset| *offset < SIZE_BYTES) +} + +unsafe impl GlobalAlloc for DefaultAlloc { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let (req_align, req_size) = prepare_layout(layout); + + let (mut filler, leftover); + let mut first_slot = FIRST_SLOT.lock_no_poison_check(); + let mut i = *first_slot; + let mut prev = None; + + loop { + if i >= SIZE_BYTES { + // out of memory + return core::ptr::null_mut(); + } + + let (len, next_ptr) = decode_slot(i); + filler = alignment_filler(i, req_align); + + if let Some(surplus) = len.checked_sub(filler + req_size) { + leftover = surplus; + + if let Some(j) = prev { + let (len, _) = decode_slot(j); + encode_slot(j, len, next_ptr); + } else { + *first_slot = next_ptr; + } + + break; + } else { + prev = Some(i); + i = next_ptr; + } + } + + drop(first_slot); + + if filler > 0 { + free(i, filler); + } + + if leftover > 0 { + free(i + filler + req_size, leftover); + } + + let start = HEAP.as_ptr() as usize; + core::ptr::from_exposed_addr_mut(start + i) + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let i = default_alloc_find(ptr).expect("invalid pointer"); + let (_, req_size) = prepare_layout(layout); + free(i, req_size); + } +} + +#[stable(feature = "alloc_system_type", since = "1.28.0")] +unsafe impl GlobalAlloc for System { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let reader = IMPL.read().expect("poisoned lock"); + if let Some(some_impl) = reader.as_ref() { + some_impl.alloc(layout) + } else { + DefaultAlloc.alloc(layout) + } + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + let reader = IMPL.read().expect("poisoned lock"); + if let Some(some_impl) = reader.as_ref() { + some_impl.alloc_zeroed(layout) + } else { + DefaultAlloc.alloc_zeroed(layout) + } + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + if let Some(i) = default_alloc_find(ptr) { + let (_, req_size) = prepare_layout(layout); + free(i, req_size); + } else { + let reader = IMPL.read().expect("poisoned lock"); + let some_impl = reader.as_ref().expect("invalid pointer"); + some_impl.dealloc(ptr, layout) + } + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + if let Some(i) = default_alloc_find(ptr) { + // if the ptr was allocated by the default allocator: + // - the current allocator is used for allocation + // - the default allocator is used for deallocation + + let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); + let new_ptr = self.alloc(new_layout); + if !new_ptr.is_null() { + let num_bytes = core::cmp::min(layout.size(), new_size); + core::ptr::copy_nonoverlapping(ptr, new_ptr, num_bytes); + + let (_, req_size) = prepare_layout(layout); + free(i, req_size); + } + new_ptr + } else { + // if the ptr was allocated by another allocator, forward the call. + + let reader = IMPL.read().expect("poisoned lock"); + let some_impl = reader.as_ref().expect("invalid pointer"); + some_impl.realloc(ptr, layout, new_size) + } + } +} diff --git a/library/std/src/sys/custom/env.rs b/library/std/src/sys/custom/env.rs new file mode 100644 index 0000000000000..d45057dd28a18 --- /dev/null +++ b/library/std/src/sys/custom/env.rs @@ -0,0 +1,11 @@ +#![unstable(issue = "none", feature = "std_internals")] + +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = "custom"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ""; + pub const DLL_EXTENSION: &str = ""; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} diff --git a/library/std/src/sys/custom/fs.rs b/library/std/src/sys/custom/fs.rs new file mode 100644 index 0000000000000..073f260f3e780 --- /dev/null +++ b/library/std/src/sys/custom/fs.rs @@ -0,0 +1,293 @@ +#![unstable(issue = "none", feature = "std_internals")] +#![allow(missing_docs)] + +use crate::custom_os_impl; +use crate::ffi::OsString; +use crate::fmt; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::path::{Path, PathBuf}; + +use crate::os::custom::time::SystemTime; + +/// Inner content of [`crate::fs::Metadata`] +#[derive(Debug, Clone)] +pub struct FileAttr { + size: u64, + perm: FilePermissions, + file_type: FileType, + modified: Option, + accessed: Option, + created: Option, +} + +fn time_error() -> io::Error { + let msg = "Couldn't obtain file modification/access/creation time"; + io::Error::new(io::ErrorKind::Unsupported, msg) +} + +impl FileAttr { + pub fn size(&self) -> u64 { + self.size + } + + pub fn perm(&self) -> FilePermissions { + self.perm + } + + pub fn file_type(&self) -> FileType { + self.file_type + } + + pub fn modified(&self) -> io::Result { + self.modified.ok_or_else(time_error) + } + + pub fn accessed(&self) -> io::Result { + self.accessed.ok_or_else(time_error) + } + + pub fn created(&self) -> io::Result { + self.created.ok_or_else(time_error) + } +} + +/// Inner content of [`crate::fs::Permissions`] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FilePermissions { + pub read_only: bool, +} + +impl FilePermissions { + pub(crate) fn readonly(&self) -> bool { + self.read_only + } + + pub(crate) fn set_readonly(&mut self, readonly: bool) { + self.read_only = readonly; + } +} + +/// Inner content of [`crate::fs::FileTimes`] +#[derive(Copy, Clone, Debug, Default)] +pub struct FileTimes { + pub accessed: Option, + pub modified: Option, +} + +impl FileTimes { + pub(crate) fn set_accessed(&mut self, t: SystemTime) { + self.accessed = Some(t); + } + + pub(crate) fn set_modified(&mut self, t: SystemTime) { + self.modified = Some(t); + } +} + +/// Inner content of [`crate::fs::FileType`] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct FileType { + pub symlink: bool, + pub directory: bool, +} + +impl FileType { + pub(crate) fn is_dir(&self) -> bool { + self.directory + } + + pub(crate) fn is_file(&self) -> bool { + !self.directory + } + + pub(crate) fn is_symlink(&self) -> bool { + self.symlink + } +} + +/// Inner content of [`crate::fs::ReadDir`] +pub type ReadDir = Box; + +/// Wrapper around `Iterator` and `Debug` +pub trait ReadDirApi: Iterator> + core::fmt::Debug {} + +/// Inner content of [`crate::fs::DirEntry`] +pub struct DirEntry { + pub path: PathBuf, + pub file_name: OsString, + pub metadata: FileAttr, + pub file_type: FileType, +} + +impl DirEntry { + pub(crate) fn path(&self) -> PathBuf { + self.path.clone() + } + + pub(crate) fn file_name(&self) -> OsString { + self.file_name.clone() + } + + pub(crate) fn metadata(&self) -> io::Result { + Ok(self.metadata.clone()) + } + + pub(crate) fn file_type(&self) -> io::Result { + Ok(self.file_type.clone()) + } +} + +/// Inner content of [`crate::fs::OpenOptions`] +#[derive(Copy, Clone, Debug, Default)] +pub struct OpenOptions { + pub read: bool, + pub write: bool, + pub append: bool, + pub truncate: bool, + pub create: bool, + pub create_new: bool, +} + +impl OpenOptions { + pub(crate) fn new() -> OpenOptions { + OpenOptions { + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + } + } + + pub(crate) fn read(&mut self, read: bool) { + self.read = read; + } + + pub(crate) fn write(&mut self, write: bool) { + self.write = write; + } + + pub(crate) fn append(&mut self, append: bool) { + self.append = append; + } + + pub(crate) fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + + pub(crate) fn create(&mut self, create: bool) { + self.create = create; + } + + pub(crate) fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; + } +} + +/// Inner content of [`crate::fs::File`] +pub struct File(pub Box); + +/// Object-oriented manipulation of a [`File`] +pub trait FileApi: fmt::Debug { + fn file_attr(&self) -> io::Result; + fn fsync(&self) -> io::Result<()>; + fn datasync(&self) -> io::Result<()>; + fn truncate(&self, _size: u64) -> io::Result<()>; + fn read(&self, _buf: &mut [u8]) -> io::Result; + fn read_vectored(&self, _bufs: &mut [IoSliceMut<'_>]) -> io::Result; + fn is_read_vectored(&self) -> bool; + fn read_buf(&self, _cursor: BorrowedCursor<'_>) -> io::Result<()>; + fn write(&self, _buf: &[u8]) -> io::Result; + fn write_vectored(&self, _bufs: &[IoSlice<'_>]) -> io::Result; + fn is_write_vectored(&self) -> bool; + fn flush(&self) -> io::Result<()>; + fn seek(&self, _pos: SeekFrom) -> io::Result; + fn duplicate(&self) -> io::Result; + fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()>; + fn set_times(&self, _times: FileTimes) -> io::Result<()>; +} + +impl File { + pub(crate) fn open(p: &Path, opts: &OpenOptions) -> io::Result { + custom_os_impl!(fs, open, p, opts) + } +} + +impl core::ops::Deref for File { + type Target = dyn FileApi; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +/// Inner content of [`crate::fs::DirBuilder`] +#[derive(Debug)] +pub struct DirBuilder; + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder + } + + pub fn mkdir(&self, p: &Path) -> io::Result<()> { + custom_os_impl!(fs, mkdir, p) + } +} + +pub fn readdir(p: &Path) -> io::Result { + custom_os_impl!(fs, read_dir, p) +} + +pub fn unlink(p: &Path) -> io::Result<()> { + custom_os_impl!(fs, unlink, p) +} + +pub fn rename(old: &Path, new: &Path) -> io::Result<()> { + custom_os_impl!(fs, rename, old, new) +} + +pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { + custom_os_impl!(fs, set_perm, p, perm) +} + +pub fn rmdir(p: &Path) -> io::Result<()> { + custom_os_impl!(fs, rmdir, p) +} + +pub fn remove_dir_all(p: &Path) -> io::Result<()> { + custom_os_impl!(fs, remove_dir_all, p) +} + +pub fn try_exists(p: &Path) -> io::Result { + custom_os_impl!(fs, try_exists, p) +} + +pub fn readlink(p: &Path) -> io::Result { + custom_os_impl!(fs, readlink, p) +} + +pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { + custom_os_impl!(fs, symlink, original, link) +} + +pub fn link(src: &Path, dst: &Path) -> io::Result<()> { + custom_os_impl!(fs, link, src, dst) +} + +pub fn stat(p: &Path) -> io::Result { + custom_os_impl!(fs, stat, p) +} + +pub fn lstat(p: &Path) -> io::Result { + custom_os_impl!(fs, lstat, p) +} + +pub fn canonicalize(p: &Path) -> io::Result { + custom_os_impl!(fs, canonicalize, p) +} + +pub fn copy(from: &Path, to: &Path) -> io::Result { + custom_os_impl!(fs, copy, from, to) +} diff --git a/library/std/src/sys/custom/locks/condvar.rs b/library/std/src/sys/custom/locks/condvar.rs new file mode 100644 index 0000000000000..c7b3c874a5bf6 --- /dev/null +++ b/library/std/src/sys/custom/locks/condvar.rs @@ -0,0 +1,57 @@ +use super::Mutex; +use super::{futex_wait, futex_wake, futex_wake_all}; +use crate::sync::atomic::{AtomicU32, Ordering::Relaxed}; +use crate::time::Duration; + +pub struct Condvar { + // The value of this atomic is simply incremented on every notification. + // This is used by `.wait()` to not miss any notifications after + // unlocking the mutex and before waiting for notifications. + futex: AtomicU32, +} + +impl Condvar { + #[inline] + #[rustc_const_stable(feature = "const_locks", since = "1.63.0")] + pub const fn new() -> Self { + Self { futex: AtomicU32::new(0) } + } + + // All the memory orderings here are `Relaxed`, + // because synchronization is done by unlocking and locking the mutex. + + pub fn notify_one(&self) { + self.futex.fetch_add(1, Relaxed); + futex_wake(&self.futex); + } + + pub fn notify_all(&self) { + self.futex.fetch_add(1, Relaxed); + futex_wake_all(&self.futex); + } + + pub unsafe fn wait(&self, mutex: &Mutex) { + self.wait_optional_timeout(mutex, None); + } + + pub unsafe fn wait_timeout(&self, mutex: &Mutex, timeout: Duration) -> bool { + self.wait_optional_timeout(mutex, Some(timeout)) + } + + unsafe fn wait_optional_timeout(&self, mutex: &Mutex, timeout: Option) -> bool { + // Examine the notification counter _before_ we unlock the mutex. + let futex_value = self.futex.load(Relaxed); + + // Unlock the mutex before going to sleep. + mutex.unlock(); + + // Wait, but only if there hasn't been any + // notification since we unlocked the mutex. + let r = futex_wait(&self.futex, futex_value, timeout); + + // Lock the mutex again. + mutex.lock(); + + r + } +} diff --git a/library/std/src/sys/custom/locks/mod.rs b/library/std/src/sys/custom/locks/mod.rs new file mode 100644 index 0000000000000..589462129b468 --- /dev/null +++ b/library/std/src/sys/custom/locks/mod.rs @@ -0,0 +1,61 @@ +/// Futex-based locking +/// +/// Platforms can supply hooks for the following operations: +/// - futex_wait +/// - futex_wake +/// - futex_wake_all +/// - kernel_hold_interrupts +/// - kernel_release_interrupts +use core::sync::atomic::AtomicU32; +use core::time::Duration; + +use crate::os::custom::futex::IMPL; + +mod condvar; +mod mutex; +mod rwlock; +pub use condvar::Condvar; +pub use mutex::Mutex; +pub use rwlock::RwLock; + +/// Wait for a futex_wake operation to wake us. +/// +/// Returns directly if the futex doesn't hold the expected value. +/// +/// Returns false on timeout, and true in all other cases. +pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) -> bool { + let reader = IMPL.read().expect("poisoned lock"); + if let Some(futex_impl) = reader.as_ref() { + futex_impl.futex_wait(futex, expected, timeout) + } else { + // as this does nothing, the caller will immediately + // try to lock again: this is spinlock behavior. + false + } +} + +/// Wake up one thread that's blocked on futex_wait on this futex. +/// +/// Returns true if this actually woke up such a thread, +/// or false if no thread was waiting on this futex. +/// +/// On some platforms, this always returns false. +pub fn futex_wake(futex: &AtomicU32) -> bool { + let reader = IMPL.read().expect("poisoned lock"); + if let Some(futex_impl) = reader.as_ref() { + futex_impl.futex_wake(futex) + } else { + // nothing to do + no-one can be woken up + false + } +} + +/// Wake up all threads that are waiting on futex_wait on this futex. +pub fn futex_wake_all(futex: &AtomicU32) { + let reader = IMPL.read().expect("poisoned lock"); + if let Some(futex_impl) = reader.as_ref() { + futex_impl.futex_wake_all(futex) + } else { + // nothing to do + } +} diff --git a/library/std/src/sys/custom/locks/mutex.rs b/library/std/src/sys/custom/locks/mutex.rs new file mode 100644 index 0000000000000..5590c37f0f3fe --- /dev/null +++ b/library/std/src/sys/custom/locks/mutex.rs @@ -0,0 +1,97 @@ +use super::{futex_wait, futex_wake}; +use crate::sync::atomic::{ + AtomicU32, + Ordering::{Acquire, Relaxed, Release}, +}; + +pub struct Mutex { + /// 0: unlocked + /// 1: locked, no other threads waiting + /// 2: locked, and other threads waiting (contended) + futex: AtomicU32, +} + +impl Mutex { + #[inline] + #[rustc_const_stable(feature = "const_locks", since = "1.63.0")] + pub const fn new() -> Self { + Self { futex: AtomicU32::new(0) } + } + + #[inline] + pub fn try_lock(&self) -> bool { + self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_ok() + } + + #[inline] + pub fn lock(&self) { + if self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_err() { + self.lock_contended(); + } + } + + #[cold] + fn lock_contended(&self) { + // Spin first to speed things up if the lock is released quickly. + let mut state = self.spin(); + + // If it's unlocked now, attempt to take the lock + // without marking it as contended. + if state == 0 { + match self.futex.compare_exchange(0, 1, Acquire, Relaxed) { + Ok(_) => return, // Locked! + Err(s) => state = s, + } + } + + loop { + // Put the lock in contended state. + // We avoid an unnecessary write if it as already set to 2, + // to be friendlier for the caches. + if state != 2 && self.futex.swap(2, Acquire) == 0 { + // We changed it from 0 to 2, so we just successfully locked it. + return; + } + + // Wait for the futex to change state, assuming it is still 2. + futex_wait(&self.futex, 2, None); + + // Spin again after waking up. + state = self.spin(); + } + } + + fn spin(&self) -> u32 { + let mut spin = 100; + loop { + // We only use `load` (and not `swap` or `compare_exchange`) + // while spinning, to be easier on the caches. + let state = self.futex.load(Relaxed); + + // We stop spinning when the mutex is unlocked (0), + // but also when it's contended (2). + if state != 1 || spin == 0 { + return state; + } + + crate::hint::spin_loop(); + spin -= 1; + } + } + + #[inline] + pub unsafe fn unlock(&self) { + if self.futex.swap(0, Release) == 2 { + // We only wake up one thread. When that thread locks the mutex, it + // will mark the mutex as contended (2) (see lock_contended above), + // which makes sure that any other waiting threads will also be + // woken up eventually. + self.wake(); + } + } + + #[cold] + fn wake(&self) { + futex_wake(&self.futex); + } +} diff --git a/library/std/src/sys/custom/locks/rwlock.rs b/library/std/src/sys/custom/locks/rwlock.rs new file mode 100644 index 0000000000000..bd1e59789723b --- /dev/null +++ b/library/std/src/sys/custom/locks/rwlock.rs @@ -0,0 +1,321 @@ +use super::{futex_wait, futex_wake, futex_wake_all}; +use crate::sync::atomic::{ + AtomicU32, + Ordering::{Acquire, Relaxed, Release}, +}; + +pub struct RwLock { + // The state consists of a 30-bit reader counter, a 'readers waiting' flag, and a 'writers waiting' flag. + // Bits 0..30: + // 0: Unlocked + // 1..=0x3FFF_FFFE: Locked by N readers + // 0x3FFF_FFFF: Write locked + // Bit 30: Readers are waiting on this futex. + // Bit 31: Writers are waiting on the writer_notify futex. + state: AtomicU32, + // The 'condition variable' to notify writers through. + // Incremented on every signal. + writer_notify: AtomicU32, +} + +const READ_LOCKED: u32 = 1; +const MASK: u32 = (1 << 30) - 1; +const WRITE_LOCKED: u32 = MASK; +const MAX_READERS: u32 = MASK - 1; +const READERS_WAITING: u32 = 1 << 30; +const WRITERS_WAITING: u32 = 1 << 31; + +#[inline] +fn is_unlocked(state: u32) -> bool { + state & MASK == 0 +} + +#[inline] +fn is_write_locked(state: u32) -> bool { + state & MASK == WRITE_LOCKED +} + +#[inline] +fn has_readers_waiting(state: u32) -> bool { + state & READERS_WAITING != 0 +} + +#[inline] +fn has_writers_waiting(state: u32) -> bool { + state & WRITERS_WAITING != 0 +} + +#[inline] +fn is_read_lockable(state: u32) -> bool { + // This also returns false if the counter could overflow if we tried to read lock it. + // + // We don't allow read-locking if there's readers waiting, even if the lock is unlocked + // and there's no writers waiting. The only situation when this happens is after unlocking, + // at which point the unlocking thread might be waking up writers, which have priority over readers. + // The unlocking thread will clear the readers waiting bit and wake up readers, if necessary. + state & MASK < MAX_READERS && !has_readers_waiting(state) && !has_writers_waiting(state) +} + +#[inline] +fn has_reached_max_readers(state: u32) -> bool { + state & MASK == MAX_READERS +} + +impl RwLock { + #[inline] + #[rustc_const_stable(feature = "const_locks", since = "1.63.0")] + pub const fn new() -> Self { + Self { state: AtomicU32::new(0), writer_notify: AtomicU32::new(0) } + } + + #[inline] + pub fn try_read(&self) -> bool { + self.state + .fetch_update(Acquire, Relaxed, |s| is_read_lockable(s).then(|| s + READ_LOCKED)) + .is_ok() + } + + #[inline] + pub fn read(&self) { + let state = self.state.load(Relaxed); + if !is_read_lockable(state) + || self + .state + .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + .is_err() + { + self.read_contended(); + } + } + + #[inline] + pub unsafe fn read_unlock(&self) { + let state = self.state.fetch_sub(READ_LOCKED, Release) - READ_LOCKED; + + // It's impossible for a reader to be waiting on a read-locked RwLock, + // except if there is also a writer waiting. + debug_assert!(!has_readers_waiting(state) || has_writers_waiting(state)); + + // Wake up a writer if we were the last reader and there's a writer waiting. + if is_unlocked(state) && has_writers_waiting(state) { + self.wake_writer_or_readers(state); + } + } + + #[cold] + fn read_contended(&self) { + let mut state = self.spin_read(); + + loop { + // If we can lock it, lock it. + if is_read_lockable(state) { + match self.state.compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + { + Ok(_) => return, // Locked! + Err(s) => { + state = s; + continue; + } + } + } + + // Check for overflow. + if has_reached_max_readers(state) { + panic!("too many active read locks on RwLock"); + } + + // Make sure the readers waiting bit is set before we go to sleep. + if !has_readers_waiting(state) { + if let Err(s) = + self.state.compare_exchange(state, state | READERS_WAITING, Relaxed, Relaxed) + { + state = s; + continue; + } + } + + // Wait for the state to change. + futex_wait(&self.state, state | READERS_WAITING, None); + + // Spin again after waking up. + state = self.spin_read(); + } + } + + #[inline] + pub fn try_write(&self) -> bool { + self.state + .fetch_update(Acquire, Relaxed, |s| is_unlocked(s).then(|| s + WRITE_LOCKED)) + .is_ok() + } + + #[inline] + pub fn write(&self) { + if self.state.compare_exchange_weak(0, WRITE_LOCKED, Acquire, Relaxed).is_err() { + self.write_contended(); + } + } + + #[inline] + pub unsafe fn write_unlock(&self) { + let state = self.state.fetch_sub(WRITE_LOCKED, Release) - WRITE_LOCKED; + + debug_assert!(is_unlocked(state)); + + if has_writers_waiting(state) || has_readers_waiting(state) { + self.wake_writer_or_readers(state); + } + } + + #[cold] + fn write_contended(&self) { + let mut state = self.spin_write(); + + let mut other_writers_waiting = 0; + + loop { + // If it's unlocked, we try to lock it. + if is_unlocked(state) { + match self.state.compare_exchange_weak( + state, + state | WRITE_LOCKED | other_writers_waiting, + Acquire, + Relaxed, + ) { + Ok(_) => return, // Locked! + Err(s) => { + state = s; + continue; + } + } + } + + // Set the waiting bit indicating that we're waiting on it. + if !has_writers_waiting(state) { + if let Err(s) = + self.state.compare_exchange(state, state | WRITERS_WAITING, Relaxed, Relaxed) + { + state = s; + continue; + } + } + + // Other writers might be waiting now too, so we should make sure + // we keep that bit on once we manage lock it. + other_writers_waiting = WRITERS_WAITING; + + // Examine the notification counter before we check if `state` has changed, + // to make sure we don't miss any notifications. + let seq = self.writer_notify.load(Acquire); + + // Don't go to sleep if the lock has become available, + // or if the writers waiting bit is no longer set. + state = self.state.load(Relaxed); + if is_unlocked(state) || !has_writers_waiting(state) { + continue; + } + + // Wait for the state to change. + futex_wait(&self.writer_notify, seq, None); + + // Spin again after waking up. + state = self.spin_write(); + } + } + + /// Wake up waiting threads after unlocking. + /// + /// If both are waiting, this will wake up only one writer, but will fall + /// back to waking up readers if there was no writer to wake up. + #[cold] + fn wake_writer_or_readers(&self, mut state: u32) { + assert!(is_unlocked(state)); + + // The readers waiting bit might be turned on at any point now, + // since readers will block when there's anything waiting. + // Writers will just lock the lock though, regardless of the waiting bits, + // so we don't have to worry about the writer waiting bit. + // + // If the lock gets locked in the meantime, we don't have to do + // anything, because then the thread that locked the lock will take + // care of waking up waiters when it unlocks. + + // If only writers are waiting, wake one of them up. + if state == WRITERS_WAITING { + match self.state.compare_exchange(state, 0, Relaxed, Relaxed) { + Ok(_) => { + self.wake_writer(); + return; + } + Err(s) => { + // Maybe some readers are now waiting too. So, continue to the next `if`. + state = s; + } + } + } + + // If both writers and readers are waiting, leave the readers waiting + // and only wake up one writer. + if state == READERS_WAITING + WRITERS_WAITING { + if self.state.compare_exchange(state, READERS_WAITING, Relaxed, Relaxed).is_err() { + // The lock got locked. Not our problem anymore. + return; + } + if self.wake_writer() { + return; + } + // No writers were actually blocked on futex_wait, so we continue + // to wake up readers instead, since we can't be sure if we notified a writer. + state = READERS_WAITING; + } + + // If readers are waiting, wake them all up. + if state == READERS_WAITING { + if self.state.compare_exchange(state, 0, Relaxed, Relaxed).is_ok() { + futex_wake_all(&self.state); + } + } + } + + /// This wakes one writer and returns true if we woke up a writer that was + /// blocked on futex_wait. + /// + /// If this returns false, it might still be the case that we notified a + /// writer that was about to go to sleep. + fn wake_writer(&self) -> bool { + self.writer_notify.fetch_add(1, Release); + futex_wake(&self.writer_notify) + // Note that FreeBSD and DragonFlyBSD don't tell us whether they woke + // up any threads or not, and always return `false` here. That still + // results in correct behaviour: it just means readers get woken up as + // well in case both readers and writers were waiting. + } + + /// Spin for a while, but stop directly at the given condition. + #[inline] + fn spin_until(&self, f: impl Fn(u32) -> bool) -> u32 { + let mut spin = 100; // Chosen by fair dice roll. + loop { + let state = self.state.load(Relaxed); + if f(state) || spin == 0 { + return state; + } + crate::hint::spin_loop(); + spin -= 1; + } + } + + #[inline] + fn spin_write(&self) -> u32 { + // Stop spinning when it's unlocked or when there's waiting writers, to keep things somewhat fair. + self.spin_until(|state| is_unlocked(state) || has_writers_waiting(state)) + } + + #[inline] + fn spin_read(&self) -> u32 { + // Stop spinning when it's unlocked or read locked, or when there's waiting threads. + self.spin_until(|state| { + !is_write_locked(state) || has_readers_waiting(state) || has_writers_waiting(state) + }) + } +} diff --git a/library/std/src/sys/custom/mod.rs b/library/std/src/sys/custom/mod.rs new file mode 100644 index 0000000000000..8d6b317a5f3cc --- /dev/null +++ b/library/std/src/sys/custom/mod.rs @@ -0,0 +1,79 @@ +//! System bindings for custom platforms + +#![unstable(issue = "none", feature = "std_internals")] + +use crate::custom_os_impl; +use crate::io as std_io; + +#[path = "../unix/cmath.rs"] +pub mod cmath; +#[path = "../unix/os_str.rs"] +pub mod os_str; +#[path = "../unix/path.rs"] +pub mod path; + +// unsupported provides a working implementation +#[path = "../unsupported/io.rs"] +pub mod io; + +// unsupported provides an empty argument iterator +#[path = "../unsupported/args.rs"] +pub mod args; + +// FIXME: unsupported provides a thread-unsafe implementations +#[path = "../unsupported/once.rs"] +pub mod once; + +#[path = "../unsupported/common.rs"] +#[deny(unsafe_op_in_unsafe_fn)] +#[allow(unused)] +mod common; +pub use common::{cleanup, init, memchr}; + +pub mod alloc; +pub mod env; +pub mod fs; +pub mod locks; +pub mod net; +pub mod os; +pub mod pipe; +pub mod process; +pub mod stdio; +pub mod thread; +pub mod thread_parking; +pub mod time; + +// really bad implementation +pub mod thread_local_key; + +pub fn decode_error_kind(errno: i32) -> std_io::ErrorKind { + custom_os_impl!(os, decode_error_kind, errno) +} + +#[inline] +pub(crate) fn is_interrupted(errno: i32) -> bool { + custom_os_impl!(os, is_interrupted, errno) +} + +pub fn abort_internal() -> ! { + fn infinite_loop() -> ! { + loop {} + } + + let rwlock = &crate::os::custom::os::IMPL; + let reader = match rwlock.read().ok() { + Some(thing) => thing, + None => infinite_loop(), + }; + let some_impl = match reader.as_ref() { + Some(thing) => thing, + None => infinite_loop(), + }; + + let exit_status = process::ExitCode::FAILURE; + some_impl.exit(exit_status.as_i32()) +} + +pub fn hashmap_random_keys() -> (u64, u64) { + custom_os_impl!(os, hashmap_random_keys) +} diff --git a/library/std/src/sys/custom/net.rs b/library/std/src/sys/custom/net.rs new file mode 100644 index 0000000000000..1cb04cee22f9e --- /dev/null +++ b/library/std/src/sys/custom/net.rs @@ -0,0 +1,216 @@ +#![unstable(issue = "none", feature = "std_internals")] +#![allow(missing_docs)] + +use crate::custom_os_impl; +use crate::fmt; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use crate::time::Duration; + +/// Inner content of [`crate::net::TcpStream`] +pub struct TcpStream(pub Box); + +/// Object-oriented manipulation of a [`TcpStream`] +pub trait TcpStreamApi: fmt::Debug { + fn set_read_timeout(&self, timeout: Option) -> io::Result<()>; + fn set_write_timeout(&self, timeout: Option) -> io::Result<()>; + fn read_timeout(&self) -> io::Result>; + fn write_timeout(&self) -> io::Result>; + fn peek(&self, bytes: &mut [u8]) -> io::Result; + fn read(&self, bytes: &mut [u8]) -> io::Result; + fn read_buf(&self, _buf: BorrowedCursor<'_>) -> io::Result<()>; + fn read_vectored(&self, slices: &mut [IoSliceMut<'_>]) -> io::Result; + fn is_read_vectored(&self) -> bool; + fn write(&self, bytes: &[u8]) -> io::Result; + fn write_vectored(&self, slices: &[IoSlice<'_>]) -> io::Result; + fn is_write_vectored(&self) -> bool; + fn peer_addr(&self) -> io::Result; + fn socket_addr(&self) -> io::Result; + fn shutdown(&self, shutdown: Shutdown) -> io::Result<()>; + fn duplicate(&self) -> io::Result; + fn set_linger(&self, linger: Option) -> io::Result<()>; + fn linger(&self) -> io::Result>; + fn set_nodelay(&self, nodelay: bool) -> io::Result<()>; + fn nodelay(&self) -> io::Result; + fn set_ttl(&self, ttl: u32) -> io::Result<()>; + fn ttl(&self) -> io::Result; + fn take_error(&self) -> io::Result>; + fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()>; +} + +impl TcpStream { + pub fn connect(addr: io::Result<&SocketAddr>) -> io::Result { + custom_os_impl!(net, tcp_connect, addr?, None) + } + + pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result { + custom_os_impl!(net, tcp_connect, addr, Some(timeout)) + } +} + +impl core::ops::Deref for TcpStream { + type Target = dyn TcpStreamApi; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +/// Inner content of [`crate::net::TcpListener`] +pub struct TcpListener(pub Box); + +/// Object-oriented manipulation of a [`TcpListener`] +pub trait TcpListenerApi: fmt::Debug { + fn socket_addr(&self) -> io::Result; + fn accept(&self) -> io::Result<(TcpStream, SocketAddr)>; + fn duplicate(&self) -> io::Result; + fn set_ttl(&self, ttl: u32) -> io::Result<()>; + fn ttl(&self) -> io::Result; + fn set_only_v6(&self, only_v6: bool) -> io::Result<()>; + fn only_v6(&self) -> io::Result; + fn take_error(&self) -> io::Result>; + fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()>; +} + +impl TcpListener { + pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result { + custom_os_impl!(net, tcp_bind, addr?) + } +} + +impl core::ops::Deref for TcpListener { + type Target = dyn TcpListenerApi; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +/// Inner content of [`crate::net::UdpSocket`] +pub struct UdpSocket(pub Box); + +/// Object-oriented manipulation of a [`UdpSocket`] +pub trait UdpSocketApi: fmt::Debug { + fn peer_addr(&self) -> io::Result; + fn socket_addr(&self) -> io::Result; + fn recv_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)>; + fn peek_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)>; + fn send_to(&self, _: &[u8], _: &SocketAddr) -> io::Result; + fn duplicate(&self) -> io::Result; + fn set_read_timeout(&self, _: Option) -> io::Result<()>; + fn set_write_timeout(&self, _: Option) -> io::Result<()>; + fn read_timeout(&self) -> io::Result>; + fn write_timeout(&self) -> io::Result>; + fn set_broadcast(&self, _: bool) -> io::Result<()>; + fn broadcast(&self) -> io::Result; + fn set_multicast_loop_v4(&self, _: bool) -> io::Result<()>; + fn multicast_loop_v4(&self) -> io::Result; + fn set_multicast_ttl_v4(&self, _: u32) -> io::Result<()>; + fn multicast_ttl_v4(&self) -> io::Result; + fn set_multicast_loop_v6(&self, _: bool) -> io::Result<()>; + fn multicast_loop_v6(&self) -> io::Result; + fn join_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()>; + fn join_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()>; + fn leave_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()>; + fn leave_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()>; + fn set_ttl(&self, _: u32) -> io::Result<()>; + fn ttl(&self) -> io::Result; + fn take_error(&self) -> io::Result>; + fn set_nonblocking(&self, _: bool) -> io::Result<()>; + fn recv(&self, _: &mut [u8]) -> io::Result; + fn peek(&self, _: &mut [u8]) -> io::Result; + fn send(&self, _: &[u8]) -> io::Result; + fn connect(&self, _: io::Result<&SocketAddr>) -> io::Result<()>; +} + +impl UdpSocket { + pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result { + custom_os_impl!(net, udp_bind, addr?) + } +} + +impl core::ops::Deref for UdpSocket { + type Target = dyn UdpSocketApi; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +/// Result of hostname/port parsing & resolution +pub struct LookupHost { + addresses: Vec, + port: u16, + i: usize, +} + +impl LookupHost { + pub fn new(addresses: Vec, port: u16) -> Self { + Self { addresses, port, i: 0 } + } + + pub(crate) fn port(&self) -> u16 { + self.port + } +} + +impl Iterator for LookupHost { + type Item = SocketAddr; + fn next(&mut self) -> Option { + let retval = self.addresses.get(self.i)?; + self.i += 1; + Some(*retval) + } +} + +impl TryFrom<&str> for LookupHost { + type Error = io::Error; + + fn try_from(v: &str) -> io::Result { + custom_os_impl!(net, lookup_str, v) + } +} + +impl<'a> TryFrom<(&'a str, u16)> for LookupHost { + type Error = io::Error; + + fn try_from(v: (&'a str, u16)) -> io::Result { + custom_os_impl!(net, lookup_tuple, v) + } +} + +#[allow(nonstandard_style)] +pub mod netc { + pub const AF_INET: u8 = 0; + pub const AF_INET6: u8 = 1; + pub type sa_family_t = u8; + + #[derive(Copy, Clone)] + pub struct in_addr { + pub s_addr: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in { + pub sin_family: sa_family_t, + pub sin_port: u16, + pub sin_addr: in_addr, + } + + #[derive(Copy, Clone)] + pub struct in6_addr { + pub s6_addr: [u8; 16], + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in6 { + pub sin6_family: sa_family_t, + pub sin6_port: u16, + pub sin6_addr: in6_addr, + pub sin6_flowinfo: u32, + pub sin6_scope_id: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr {} +} diff --git a/library/std/src/sys/custom/os.rs b/library/std/src/sys/custom/os.rs new file mode 100644 index 0000000000000..f2de4e149005c --- /dev/null +++ b/library/std/src/sys/custom/os.rs @@ -0,0 +1,178 @@ +#![unstable(issue = "none", feature = "std_internals")] +#![allow(missing_docs)] + +use crate::custom_os_impl; +use crate::error::Error; +use crate::ffi::{OsStr, OsString}; +use crate::fmt; +use crate::io; +use crate::path::{self, PathBuf}; +use crate::str::Split; +use crate::vec; + +/// Inner content of [`crate::env::SplitPaths`] +pub struct SplitPaths<'a>(Split<'a, &'static str>); + +impl<'a> Iterator for SplitPaths<'a> { + type Item = PathBuf; + + fn next(&mut self) -> Option { + self.0.next().map(|s| s.into()) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +/// Inner content of [`crate::env::JoinPathsError`] +#[derive(Debug)] +pub struct JoinPathsError(pub &'static str); + +#[stable(feature = "env", since = "1.0.0")] +impl Error for JoinPathsError { + #[allow(deprecated, deprecated_in_future)] + fn description(&self) -> &str { + self.0 + } +} + +impl core::fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +pub fn errno() -> i32 { + custom_os_impl!(os, errno) +} + +pub fn error_string(errno: i32) -> String { + custom_os_impl!(os, error_string, errno) +} + +pub fn getcwd() -> io::Result { + custom_os_impl!(os, getcwd) +} + +pub fn chdir(path: &path::Path) -> io::Result<()> { + custom_os_impl!(os, chdir, path) +} + +pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> { + let delim = custom_os_impl!(os, env_path_delim); + let as_str = unparsed.to_str().expect("invalid PATH environment variable"); + + SplitPaths(as_str.split(delim)) +} + +pub fn join_paths(mut paths: I) -> Result +where + I: Iterator, + T: AsRef, +{ + let delim = custom_os_impl!(os, env_path_delim); + let mut joined = match paths.next() { + Some(first) => first.as_ref().to_os_string(), + None => OsString::new(), + }; + + while let Some(path) = paths.next() { + joined.push(delim); + joined.push(path); + } + + Ok(joined) +} + +pub fn current_exe() -> io::Result { + custom_os_impl!(os, current_exe) +} + +/// An environment variable definition +#[derive(Debug)] +pub struct Variable { + pub name: OsString, + pub value: OsString, +} + +/// An Iterator on environment variables +pub struct Env { + pub iter: vec::IntoIter, +} + +// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. +pub struct EnvStrDebug<'a> { + slice: &'a [Variable], +} + +impl fmt::Debug for EnvStrDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { slice } = self; + f.debug_list() + .entries( + slice.iter().map(|var| (var.name.to_str().unwrap(), var.value.to_str().unwrap())), + ) + .finish() + } +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self { iter } = self; + EnvStrDebug { slice: iter.as_slice() } + } +} + +impl fmt::Debug for Env { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { iter } = self; + f.debug_list().entries(iter.as_slice()).finish() + } +} + +impl !Send for Env {} +impl !Sync for Env {} + +impl Iterator for Env { + type Item = (OsString, OsString); + fn next(&mut self) -> Option<(OsString, OsString)> { + self.iter.next().map(|var| (var.name, var.value)) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +pub fn env() -> Env { + custom_os_impl!(os, env) +} + +pub fn getenv(var_name: &OsStr) -> Option { + custom_os_impl!(os, get_env, var_name) +} + +pub fn setenv(var_name: &OsStr, value: &OsStr) -> io::Result<()> { + custom_os_impl!(os, set_env, var_name, value) +} + +pub fn unsetenv(var_name: &OsStr) -> io::Result<()> { + custom_os_impl!(os, unset_env, var_name) +} + +pub fn temp_dir() -> PathBuf { + custom_os_impl!(os, temp_dir) +} + +pub fn home_dir() -> Option { + custom_os_impl!(os, home_dir) +} + +pub fn exit(code: i32) -> ! { + custom_os_impl!(os, exit, code) +} + +pub fn getpid() -> u32 { + custom_os_impl!(os, get_pid) +} diff --git a/library/std/src/sys/custom/pipe.rs b/library/std/src/sys/custom/pipe.rs new file mode 100644 index 0000000000000..8ce31ec0eb372 --- /dev/null +++ b/library/std/src/sys/custom/pipe.rs @@ -0,0 +1,21 @@ +#![unstable(issue = "none", feature = "std_internals")] + +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; + +pub type AnonPipe = Box; + +pub trait AnonPipeApi { + fn read(&self, _buf: &mut [u8]) -> io::Result; + fn read_buf(&self, _buf: BorrowedCursor<'_>) -> io::Result<()>; + fn read_vectored(&self, _bufs: &mut [IoSliceMut<'_>]) -> io::Result; + fn is_read_vectored(&self) -> bool; + fn read_to_end(&self, _buf: &mut Vec) -> io::Result; + fn write(&self, _buf: &[u8]) -> io::Result; + fn write_vectored(&self, _bufs: &[IoSlice<'_>]) -> io::Result; + fn is_write_vectored(&self) -> bool; + fn diverge(&self) -> !; +} + +pub fn read2(_p1: AnonPipe, _v1: &mut Vec, _p2: AnonPipe, _v2: &mut Vec) -> io::Result<()> { + todo!() +} diff --git a/library/std/src/sys/custom/process.rs b/library/std/src/sys/custom/process.rs new file mode 100644 index 0000000000000..ea8e2d8b6c32f --- /dev/null +++ b/library/std/src/sys/custom/process.rs @@ -0,0 +1,237 @@ +#![unstable(issue = "none", feature = "std_internals")] +#![allow(missing_docs)] + +use crate::custom_os_impl; +use crate::ffi::{OsStr, OsString}; +use crate::fmt; +use crate::io; +use crate::num::NonZeroI32; +use crate::path::Path; +use crate::sys::fs::File; +use crate::sys::os::error_string; +use crate::sys::pipe::AnonPipe; +use crate::sys_common::process::{CommandEnv, CommandEnvs}; + +pub type EnvKey = OsString; + +/// Inner content of [`crate::process::Command`] +#[derive(Debug)] +pub struct Command { + /// Environment (variables) + pub env: CommandEnv, + /// Name/Path of the program to run + pub program: OsString, + /// Initial working directory + pub cwd: Option, + /// Arguments + pub args: Vec, + /// Will have a defined value when passed to the custom platform + pub stdin: Option, + /// Will have a defined value when passed to the custom platform + pub stdout: Option, + /// Will have a defined value when passed to the custom platform + pub stderr: Option, + pub needs_stdin: bool, +} + +/// Passed back to std::process with the pipes connected to +/// the child, if any were requested +pub struct StdioPipes { + pub stdin: Option, + pub stdout: Option, + pub stderr: Option, +} + +/// Defines the source for IO pipes of the child process +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Stdio { + Inherit, + Null, + MakePipe, +} + +impl Command { + pub(crate) fn new(program: &OsStr) -> Command { + Command { + env: Default::default(), + program: program.into(), + cwd: None, + args: Vec::new(), + stdin: None, + stdout: None, + stderr: None, + needs_stdin: /* will be overwritten */ false, + } + } + + pub(crate) fn arg(&mut self, arg: &OsStr) { + self.args.push(arg.into()) + } + + pub(crate) fn env_mut(&mut self) -> &mut CommandEnv { + &mut self.env + } + + pub(crate) fn cwd(&mut self, dir: &OsStr) { + self.cwd = Some(dir.into()) + } + + pub(crate) fn stdin(&mut self, stdin: Stdio) { + self.stdin = Some(stdin) + } + + pub(crate) fn stdout(&mut self, stdout: Stdio) { + self.stdout = Some(stdout) + } + + pub(crate) fn stderr(&mut self, stderr: Stdio) { + self.stderr = Some(stderr) + } + + pub(crate) fn get_program(&self) -> &OsStr { + &self.program + } + + pub(crate) fn get_args(&self) -> CommandArgs<'_> { + CommandArgs { iter: self.args.iter() } + } + + pub(crate) fn get_envs(&self) -> CommandEnvs<'_> { + self.env.iter() + } + + pub(crate) fn get_current_dir(&self) -> Option<&Path> { + self.cwd.as_ref().map(|string| Path::new(string)) + } + + pub(crate) fn spawn( + &mut self, + default: Stdio, + needs_stdin: bool, + ) -> io::Result<(Process, StdioPipes)> { + self.stdin.get_or_insert(default); + self.stdout.get_or_insert(default); + self.stderr.get_or_insert(default); + self.needs_stdin = needs_stdin; + + custom_os_impl!(process, spawn, self) + } + + pub(crate) fn output(&mut self) -> io::Result<(ExitStatus, Vec, Vec)> { + todo!() + } +} + +impl From for Stdio { + fn from(pipe: AnonPipe) -> Stdio { + pipe.diverge() + } +} + +impl From for Stdio { + fn from(_file: File) -> Stdio { + todo!() + } +} + +/// Success must be internally represented as zero +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +#[repr(transparent)] +#[non_exhaustive] +pub struct ExitStatus(pub i32); + +impl ExitStatus { + pub fn exit_ok(&self) -> Result<(), ExitStatusError> { + match self.0 == ExitCode::SUCCESS.as_i32() { + true => Ok(()), + false => Err(ExitStatusError(*self)), + } + } + + pub fn code(&self) -> Option { + Some(self.0) + } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", error_string(self.0)) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct ExitStatusError(ExitStatus); + +impl Into for ExitStatusError { + fn into(self) -> ExitStatus { + self.0 + } +} + +impl ExitStatusError { + pub fn code(self) -> Option { + self.0.code().map(|c| NonZeroI32::try_from(c).expect("invalid ExitStatus")) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitCode(bool); + +impl ExitCode { + pub const SUCCESS: ExitCode = ExitCode(false); + pub const FAILURE: ExitCode = ExitCode(true); + + pub fn as_i32(&self) -> i32 { + self.0 as i32 + } +} + +impl From for ExitCode { + fn from(code: u8) -> Self { + match code { + 0 => Self::SUCCESS, + 1..=255 => Self::FAILURE, + } + } +} + +/// Inner content of [`crate::process::Child`] +pub type Process = Box; + +/// Object-oriented manipulation of a [`Process`] +pub trait ProcessApi { + fn id(&self) -> u32; + fn kill(&mut self) -> io::Result<()>; + fn wait(&mut self) -> io::Result; + fn try_wait(&mut self) -> io::Result>; +} + +pub struct CommandArgs<'a> { + iter: crate::slice::Iter<'a, OsString>, +} + +impl<'a> Iterator for CommandArgs<'a> { + type Item = &'a OsStr; + fn next(&mut self) -> Option<&'a OsStr> { + self.iter.next().map(|string| &**string) + } + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a> ExactSizeIterator for CommandArgs<'a> { + fn len(&self) -> usize { + self.iter.len() + } + fn is_empty(&self) -> bool { + self.iter.is_empty() + } +} + +impl<'a> fmt::Debug for CommandArgs<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter.clone()).finish() + } +} diff --git a/library/std/src/sys/custom/stdio.rs b/library/std/src/sys/custom/stdio.rs new file mode 100644 index 0000000000000..fb02024b9201f --- /dev/null +++ b/library/std/src/sys/custom/stdio.rs @@ -0,0 +1,61 @@ +#![unstable(issue = "none", feature = "std_internals")] +use crate::custom_os_impl; +use crate::io; + +pub struct Stdin; +pub struct Stdout; +pub struct Stderr; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + custom_os_impl!(stdio, read_stdin, buf) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + custom_os_impl!(stdio, write_stdout, buf) + } + + fn flush(&mut self) -> io::Result<()> { + custom_os_impl!(stdio, flush_stdout) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + custom_os_impl!(stdio, write_stderr, buf) + } + + fn flush(&mut self) -> io::Result<()> { + custom_os_impl!(stdio, flush_stderr) + } +} + +pub const STDIN_BUF_SIZE: usize = 0; + +pub fn is_ebadf(err: &io::Error) -> bool { + custom_os_impl!(stdio, is_ebadf, err) +} + +pub fn panic_output() -> Option> { + custom_os_impl!(stdio, panic_output) +} diff --git a/library/std/src/sys/custom/thread.rs b/library/std/src/sys/custom/thread.rs new file mode 100644 index 0000000000000..bd2ae7cfc326c --- /dev/null +++ b/library/std/src/sys/custom/thread.rs @@ -0,0 +1,57 @@ +#![unstable(issue = "none", feature = "std_internals")] +#![allow(missing_docs)] + +use crate::custom_os_impl; +use crate::ffi::CStr; +use crate::io; +use crate::num::NonZeroUsize; +use crate::time::Duration; + +/// Inner content of [`crate::thread::Thread`] +#[derive(Debug)] +pub struct Thread(pub Box); + +pub const DEFAULT_MIN_STACK_SIZE: usize = 4096; + +/// Object-oriented manipulation of a [`Thread`] +pub trait ThreadApi: crate::fmt::Debug { + // self will be dropped upon return + fn join(&self); +} + +impl Thread { + // unsafe: see thread::Builder::spawn_unchecked for safety requirements + pub unsafe fn new(stack: usize, p: Box) -> io::Result { + custom_os_impl!(thread, new, stack, p) + } + + pub fn yield_now() { + custom_os_impl!(thread, yield_now) + } + + pub fn set_name(name: &CStr) { + custom_os_impl!(thread, set_name, name) + } + + pub fn sleep(dur: Duration) { + custom_os_impl!(thread, sleep, dur) + } + + pub fn join(self) { + self.0.join() + } +} + +pub fn available_parallelism() -> io::Result { + custom_os_impl!(thread, available_parallelism) +} + +pub mod guard { + pub type Guard = !; + pub unsafe fn current() -> Option { + None + } + pub unsafe fn init() -> Option { + None + } +} diff --git a/library/std/src/sys/custom/thread_local_key.rs b/library/std/src/sys/custom/thread_local_key.rs new file mode 100644 index 0000000000000..1f84a98921505 --- /dev/null +++ b/library/std/src/sys/custom/thread_local_key.rs @@ -0,0 +1,117 @@ +use crate::os::custom::thread::{ThreadId, IMPL}; +use crate::sync::{Mutex, RwLock}; + +pub type Key = usize; + +const NULL: *mut u8 = core::ptr::null_mut(); + +type Destructor = Option; + +fn thread_id() -> Option { + let reader = IMPL.read_no_poison_check(); + reader.as_ref()?.current_thread_id() +} + +// safety: each ThreadStorage is virtually owned by one thread +unsafe impl Sync for ThreadStorage {} +unsafe impl Send for ThreadStorage {} + +struct ThreadStorage(Mutex>); + +impl ThreadStorage { + pub const fn new() -> Self { + Self(Mutex::new(Vec::new())) + } + + pub fn get(&self, key: Key) -> *mut u8 { + let locked = self.0.lock_no_poison_check(); + match locked.get(key - 1) { + Some(pointer_ref) => *pointer_ref, + None => NULL, + } + } + + pub fn set(&self, key: Key, value: *mut u8) { + let mut locked = self.0.lock_no_poison_check(); + locked.resize(key, NULL); + locked[key - 1] = value; + } +} + +struct Slots { + destructors: RwLock>, + initial_thread: ThreadStorage, + other_threads: RwLock>, +} + +impl Slots { + pub const fn new() -> Self { + Self { + destructors: RwLock::new(Vec::new()), + initial_thread: ThreadStorage::new(), + other_threads: RwLock::new(Vec::new()), + } + } + + pub fn push(&self, destructor: Destructor) -> Key { + let mut destructors = self.destructors.write_no_poison_check(); + destructors.push(destructor); + destructors.len() + } + + pub fn get_dtor(&self, key: Key) -> Destructor { + let destructors = self.destructors.read_no_poison_check(); + destructors[key - 1] + } + + pub fn with_thread_storage T>(&self, callback: F) -> T { + if let Some(id) = thread_id() { + loop { + let finder = |(tid, _): &(ThreadId, ThreadStorage)| tid.cmp(&id); + + { + let other_threads = self.other_threads.read_no_poison_check(); + let position = other_threads.binary_search_by(finder); + + if let Ok(i) = position { + return callback(&other_threads[i].1); + } + } + + // cannot re-use `position` (race condition) + let mut other_threads = self.other_threads.write_no_poison_check(); + let i = other_threads.binary_search_by(finder).unwrap_err(); + other_threads.insert(i, (id, ThreadStorage::new())); + } + } else { + callback(&self.initial_thread) + } + } +} + +static SLOTS: Slots = Slots::new(); + +#[inline] +pub unsafe fn create(dtor: Option) -> Key { + SLOTS.push(dtor) +} + +#[inline] +pub unsafe fn set(key: Key, value: *mut u8) { + SLOTS.with_thread_storage(|storage| storage.set(key, value)) +} + +#[inline] +pub unsafe fn get(key: Key) -> *mut u8 { + SLOTS.with_thread_storage(|storage| storage.get(key)) +} + +#[inline] +pub unsafe fn destroy(key: Key) { + let value = get(key); + if !value.is_null() { + if let Some(destructor) = SLOTS.get_dtor(key) { + destructor(value) + } + } +} diff --git a/library/std/src/sys/custom/thread_parking.rs b/library/std/src/sys/custom/thread_parking.rs new file mode 100644 index 0000000000000..e1501ad73ad24 --- /dev/null +++ b/library/std/src/sys/custom/thread_parking.rs @@ -0,0 +1,13 @@ +#![unstable(issue = "none", feature = "std_internals")] + +use crate::pin::Pin; +use crate::time::Duration; + +pub struct Parker {} + +impl Parker { + pub unsafe fn new_in_place(_parker: *mut Parker) {} + pub unsafe fn park(self: Pin<&Self>) {} + pub unsafe fn park_timeout(self: Pin<&Self>, _dur: Duration) {} + pub fn unpark(self: Pin<&Self>) {} +} diff --git a/library/std/src/sys/custom/time.rs b/library/std/src/sys/custom/time.rs new file mode 100644 index 0000000000000..a4160060126f5 --- /dev/null +++ b/library/std/src/sys/custom/time.rs @@ -0,0 +1,51 @@ +#![unstable(issue = "none", feature = "std_internals")] +#![allow(missing_docs)] + +use crate::custom_os_impl; +use crate::time::Duration; + +/// Inner content of [`crate::time::Instant`] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Instant(pub Duration); + +impl Instant { + pub fn now() -> Instant { + custom_os_impl!(time, now_instant) + } + + pub fn checked_sub_instant(&self, other: &Instant) -> Option { + self.0.checked_sub(other.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + Some(Instant(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + Some(Instant(self.0.checked_sub(*other)?)) + } +} + +/// Inner content of [`crate::time::SystemTime`] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SystemTime(pub Duration); + +pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); + +impl SystemTime { + pub fn now() -> SystemTime { + custom_os_impl!(time, now_systime) + } + + pub fn sub_time(&self, other: &SystemTime) -> Result { + self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + Some(SystemTime(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + Some(SystemTime(self.0.checked_sub(*other)?)) + } +} diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index 159ffe7ac9635..86e3645303fac 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -41,6 +41,9 @@ cfg_if::cfg_if! { } else if #[cfg(target_os = "wasi")] { mod wasi; pub use self::wasi::*; + } else if #[cfg(target_os = "custom")] { + mod custom; + pub use self::custom::*; } else if #[cfg(target_family = "wasm")] { mod wasm; pub use self::wasm::*; diff --git a/library/std/src/sys_common/mod.rs b/library/std/src/sys_common/mod.rs index e18638f2a5f54..817706220ef73 100644 --- a/library/std/src/sys_common/mod.rs +++ b/library/std/src/sys_common/mod.rs @@ -45,6 +45,7 @@ cfg_if::cfg_if! { cfg_if::cfg_if! { if #[cfg(any(target_os = "l4re", target_os = "uefi", + target_os = "custom", feature = "restricted-std", all(target_family = "wasm", not(target_os = "emscripten")), target_os = "xous", diff --git a/library/std/src/sys_common/process.rs b/library/std/src/sys_common/process.rs index 4d295cf0f09d5..113700ed0cde1 100644 --- a/library/std/src/sys_common/process.rs +++ b/library/std/src/sys_common/process.rs @@ -9,7 +9,7 @@ use crate::io; use crate::sys::pipe::read2; use crate::sys::process::{EnvKey, ExitStatus, Process, StdioPipes}; -// Stores a set of changes to an environment +/// Stores a set of changes to an environment #[derive(Clone)] pub struct CommandEnv { clear: bool, diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 8b8d4b237953a..a901c04a9ab01 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -134,7 +134,7 @@ const EXTRA_CHECK_CFGS: &[(Option, &str, Option<&[&'static str]>)] = &[ (Some(Mode::Std), "target_vendor", Some(&["unikraft"])), (Some(Mode::Std), "target_env", Some(&["libnx"])), // #[cfg(bootstrap)] hurd - (Some(Mode::Std), "target_os", Some(&["teeos", "hurd"])), + (Some(Mode::Std), "target_os", Some(&["teeos", "hurd", "custom"])), (Some(Mode::Rustc), "target_os", Some(&["hurd"])), // #[cfg(bootstrap)] mips32r6, mips64r6 (