From 8fad3a17ca443c00a05c121730c8d4f60e73aff8 Mon Sep 17 00:00:00 2001 From: "U. Lasiotus" Date: Wed, 24 Sep 2025 12:11:44 -0700 Subject: [PATCH 01/24] tidy: allow stdlib to depend on moto-rt As part of work to add stdlib support for Motor OS. --- src/tools/tidy/src/deps.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 3f40bef821c9d..3164dcc77be1d 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -569,6 +569,7 @@ const PERMITTED_STDLIB_DEPENDENCIES: &[&str] = &[ "libc", "memchr", "miniz_oxide", + "moto-rt", "object", "r-efi", "r-efi-alloc", From a828ffcf5f04be5cdd91b5fad608102eabc17ec7 Mon Sep 17 00:00:00 2001 From: "U. Lasiotus" Date: Tue, 7 Oct 2025 18:56:25 -0700 Subject: [PATCH 02/24] Add Motor OS std library port Motor OS was added as a no-std Tier-3 target in https://github.com/rust-lang/rust/pull/146848 as x86_64-unknown-motor. This patch/PR adds the std library for Motor OS. While the patch may seem large, all it does is proxy std pal calls to moto-rt. When there is some non-trivial code (e.g. thread::spawn), it is quite similar, and often identical, to what other platforms do. --- library/Cargo.lock | 11 + library/std/Cargo.toml | 3 + library/std/build.rs | 1 + library/std/src/os/fd/owned.rs | 22 +- library/std/src/os/fd/raw.rs | 10 +- library/std/src/os/mod.rs | 11 +- library/std/src/os/motor/ffi.rs | 37 ++ library/std/src/os/motor/mod.rs | 4 + library/std/src/os/motor/process.rs | 15 + library/std/src/sys/alloc/mod.rs | 3 + library/std/src/sys/alloc/motor.rs | 28 + library/std/src/sys/anonymous_pipe/mod.rs | 4 + library/std/src/sys/anonymous_pipe/motor.rs | 11 + library/std/src/sys/args/mod.rs | 5 + library/std/src/sys/args/motor.rs | 13 + library/std/src/sys/env/mod.rs | 5 + library/std/src/sys/env/motor.rs | 27 + library/std/src/sys/fd/mod.rs | 4 + library/std/src/sys/fd/motor.rs | 124 +++++ library/std/src/sys/fs/mod.rs | 4 + library/std/src/sys/fs/motor.rs | 478 ++++++++++++++++ library/std/src/sys/io/is_terminal/motor.rs | 6 + library/std/src/sys/io/mod.rs | 4 + library/std/src/sys/net/connection/mod.rs | 4 + library/std/src/sys/net/connection/motor.rs | 521 ++++++++++++++++++ library/std/src/sys/pal/mod.rs | 4 + library/std/src/sys/pal/motor/mod.rs | 77 +++ library/std/src/sys/pal/motor/os.rs | 100 ++++ library/std/src/sys/pal/motor/pipe.rs | 121 ++++ library/std/src/sys/pal/motor/time.rs | 1 + library/std/src/sys/path/unix.rs | 2 +- library/std/src/sys/personality/mod.rs | 2 +- library/std/src/sys/process/mod.rs | 6 + library/std/src/sys/process/motor.rs | 313 +++++++++++ library/std/src/sys/random/mod.rs | 4 + library/std/src/sys/random/motor.rs | 3 + library/std/src/sys/stdio/mod.rs | 4 + library/std/src/sys/stdio/motor.rs | 232 ++++++++ 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/motor.rs | 63 +++ library/std/src/sys/thread_local/mod.rs | 8 + 46 files changed, 2296 insertions(+), 8 deletions(-) create mode 100644 library/std/src/os/motor/ffi.rs create mode 100644 library/std/src/os/motor/mod.rs create mode 100644 library/std/src/os/motor/process.rs create mode 100644 library/std/src/sys/alloc/motor.rs create mode 100644 library/std/src/sys/anonymous_pipe/motor.rs create mode 100644 library/std/src/sys/args/motor.rs create mode 100644 library/std/src/sys/env/motor.rs create mode 100644 library/std/src/sys/fd/motor.rs create mode 100644 library/std/src/sys/fs/motor.rs create mode 100644 library/std/src/sys/io/is_terminal/motor.rs create mode 100644 library/std/src/sys/net/connection/motor.rs create mode 100644 library/std/src/sys/pal/motor/mod.rs create mode 100644 library/std/src/sys/pal/motor/os.rs create mode 100644 library/std/src/sys/pal/motor/pipe.rs create mode 100644 library/std/src/sys/pal/motor/time.rs create mode 100644 library/std/src/sys/process/motor.rs create mode 100644 library/std/src/sys/random/motor.rs create mode 100644 library/std/src/sys/stdio/motor.rs create mode 100644 library/std/src/sys/thread/motor.rs diff --git a/library/Cargo.lock b/library/Cargo.lock index 47fbf5169f491..1156d6925cf2c 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -166,6 +166,16 @@ dependencies = [ "rustc-std-workspace-core", ] +[[package]] +name = "moto-rt" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058a2807a30527bee4c30df7ababe971cdde94372d4dbd1ff145bb403381436c" +dependencies = [ + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + [[package]] name = "object" version = "0.37.3" @@ -316,6 +326,7 @@ dependencies = [ "hermit-abi", "libc", "miniz_oxide", + "moto-rt", "object", "panic_abort", "panic_unwind", diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 779b07ce240a6..b7bf5ea7ba738 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -70,6 +70,9 @@ fortanix-sgx-abi = { version = "0.6.1", features = [ 'rustc-dep-of-std', ], public = true } +[target.'cfg(target_os = "motor")'.dependencies] +moto-rt = { version = "0.15", features = ['rustc-dep-of-std'], public = true } + [target.'cfg(target_os = "hermit")'.dependencies] hermit-abi = { version = "0.5.0", features = [ 'rustc-dep-of-std', diff --git a/library/std/build.rs b/library/std/build.rs index 8a5a785060c85..bee28e88491d0 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -30,6 +30,7 @@ fn main() { || target_os == "windows" || target_os == "fuchsia" || (target_vendor == "fortanix" && target_env == "sgx") + || target_os == "motor" || target_os == "hermit" || target_os == "trusty" || target_os == "l4re" diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs index 10e1e73a115bd..6a0e7a640028b 100644 --- a/library/std/src/os/fd/owned.rs +++ b/library/std/src/os/fd/owned.rs @@ -3,6 +3,9 @@ #![stable(feature = "io_safety", since = "1.63.0")] #![deny(unsafe_op_in_unsafe_fn)] +#[cfg(target_os = "motor")] +use moto_rt::libc; + use super::raw::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; #[cfg(not(target_os = "trusty"))] use crate::fs; @@ -12,7 +15,8 @@ use crate::mem::ManuallyDrop; target_arch = "wasm32", target_env = "sgx", target_os = "hermit", - target_os = "trusty" + target_os = "trusty", + target_os = "motor" )))] use crate::sys::cvt; #[cfg(not(target_os = "trusty"))] @@ -95,7 +99,12 @@ impl OwnedFd { impl BorrowedFd<'_> { /// Creates a new `OwnedFd` instance that shares the same underlying file /// description as the existing `BorrowedFd` instance. - #[cfg(not(any(target_arch = "wasm32", target_os = "hermit", target_os = "trusty")))] + #[cfg(not(any( + target_arch = "wasm32", + target_os = "hermit", + target_os = "trusty", + target_os = "motor" + )))] #[stable(feature = "io_safety", since = "1.63.0")] pub fn try_clone_to_owned(&self) -> crate::io::Result { // We want to atomically duplicate this file descriptor and set the @@ -123,6 +132,15 @@ impl BorrowedFd<'_> { pub fn try_clone_to_owned(&self) -> crate::io::Result { Err(crate::io::Error::UNSUPPORTED_PLATFORM) } + + /// Creates a new `OwnedFd` instance that shares the same underlying file + /// description as the existing `BorrowedFd` instance. + #[cfg(target_os = "motor")] + #[stable(feature = "io_safety", since = "1.63.0")] + pub fn try_clone_to_owned(&self) -> crate::io::Result { + let fd = moto_rt::fs::duplicate(self.as_raw_fd()).map_err(crate::sys::map_motor_error)?; + Ok(unsafe { OwnedFd::from_raw_fd(fd) }) + } } #[stable(feature = "io_safety", since = "1.63.0")] diff --git a/library/std/src/os/fd/raw.rs b/library/std/src/os/fd/raw.rs index 34a6cf1a8b84d..c01e6b83cd366 100644 --- a/library/std/src/os/fd/raw.rs +++ b/library/std/src/os/fd/raw.rs @@ -4,13 +4,17 @@ #[cfg(target_os = "hermit")] use hermit_abi as libc; +#[cfg(target_os = "motor")] +use moto_rt::libc; +#[cfg(target_os = "motor")] +use super::owned::OwnedFd; #[cfg(not(target_os = "trusty"))] use crate::fs; use crate::io; #[cfg(target_os = "hermit")] use crate::os::hermit::io::OwnedFd; -#[cfg(not(target_os = "hermit"))] +#[cfg(all(not(target_os = "hermit"), not(target_os = "motor")))] use crate::os::raw; #[cfg(all(doc, not(target_arch = "wasm32")))] use crate::os::unix::io::AsFd; @@ -23,10 +27,10 @@ use crate::sys_common::{AsInner, FromInner, IntoInner}; /// Raw file descriptors. #[stable(feature = "rust1", since = "1.0.0")] -#[cfg(not(target_os = "hermit"))] +#[cfg(all(not(target_os = "hermit"), not(target_os = "motor")))] pub type RawFd = raw::c_int; #[stable(feature = "rust1", since = "1.0.0")] -#[cfg(target_os = "hermit")] +#[cfg(any(target_os = "hermit", target_os = "motor"))] pub type RawFd = i32; /// A trait to extract the raw file descriptor from an underlying object. diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs index fd7a11433af1b..76374402be4b3 100644 --- a/library/std/src/os/mod.rs +++ b/library/std/src/os/mod.rs @@ -155,6 +155,8 @@ pub mod ios; pub mod l4re; #[cfg(target_os = "macos")] pub mod macos; +#[cfg(target_os = "motor")] +pub mod motor; #[cfg(target_os = "netbsd")] pub mod netbsd; #[cfg(target_os = "nto")] @@ -182,7 +184,14 @@ pub mod vxworks; #[cfg(target_os = "xous")] pub mod xous; -#[cfg(any(unix, target_os = "hermit", target_os = "trusty", target_os = "wasi", doc))] +#[cfg(any( + unix, + target_os = "hermit", + target_os = "trusty", + target_os = "wasi", + target_os = "motor", + doc +))] pub mod fd; #[cfg(any(target_os = "linux", target_os = "android", target_os = "cygwin", doc))] diff --git a/library/std/src/os/motor/ffi.rs b/library/std/src/os/motor/ffi.rs new file mode 100644 index 0000000000000..509fe641bb353 --- /dev/null +++ b/library/std/src/os/motor/ffi.rs @@ -0,0 +1,37 @@ +//! Motor OS-specific extensions to primitives in the [`std::ffi`] module. +#![unstable(feature = "motor_ext", issue = "147456")] + +use crate::ffi::{OsStr, OsString}; +use crate::sealed::Sealed; + +/// Motor OS-specific extensions to [`OsString`]. +/// +/// This trait is sealed: it cannot be implemented outside the standard library. +/// This is so that future additional methods are not breaking changes. +pub trait OsStringExt: Sealed { + /// Motor OS strings are utf-8, and thus just strings. + fn as_str(&self) -> &str; +} + +impl OsStringExt for OsString { + #[inline] + fn as_str(&self) -> &str { + self.to_str().unwrap() + } +} + +/// Motor OS-specific extensions to [`OsString`]. +/// +/// This trait is sealed: it cannot be implemented outside the standard library. +/// This is so that future additional methods are not breaking changes. +pub trait OsStrExt: Sealed { + /// Motor OS strings are utf-8, and thus just strings. + fn as_str(&self) -> &str; +} + +impl OsStrExt for OsStr { + #[inline] + fn as_str(&self) -> &str { + self.to_str().unwrap() + } +} diff --git a/library/std/src/os/motor/mod.rs b/library/std/src/os/motor/mod.rs new file mode 100644 index 0000000000000..18da079c74a15 --- /dev/null +++ b/library/std/src/os/motor/mod.rs @@ -0,0 +1,4 @@ +#![unstable(feature = "motor_ext", issue = "147456")] + +pub mod ffi; +pub mod process; diff --git a/library/std/src/os/motor/process.rs b/library/std/src/os/motor/process.rs new file mode 100644 index 0000000000000..015fbcb97f97d --- /dev/null +++ b/library/std/src/os/motor/process.rs @@ -0,0 +1,15 @@ +#![unstable(feature = "motor_ext", issue = "147456")] + +use crate::sealed::Sealed; +use crate::sys_common::AsInner; + +pub trait ChildExt: Sealed { + /// Extracts the main thread raw handle, without taking ownership + fn sys_handle(&self) -> u64; +} + +impl ChildExt for crate::process::Child { + fn sys_handle(&self) -> u64 { + self.as_inner().handle() + } +} diff --git a/library/std/src/sys/alloc/mod.rs b/library/std/src/sys/alloc/mod.rs index 2045b2fecc6ac..f2f1d1c7feceb 100644 --- a/library/std/src/sys/alloc/mod.rs +++ b/library/std/src/sys/alloc/mod.rs @@ -83,6 +83,9 @@ cfg_select! { target_os = "hermit" => { mod hermit; } + target_os = "motor" => { + mod motor; + } all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; } diff --git a/library/std/src/sys/alloc/motor.rs b/library/std/src/sys/alloc/motor.rs new file mode 100644 index 0000000000000..271e3c40c26ae --- /dev/null +++ b/library/std/src/sys/alloc/motor.rs @@ -0,0 +1,28 @@ +use crate::alloc::{GlobalAlloc, Layout, System}; + +#[stable(feature = "alloc_system_type", since = "1.28.0")] +unsafe impl GlobalAlloc for System { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // SAFETY: same requirements as in GlobalAlloc::alloc. + moto_rt::alloc::alloc(layout) + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + // SAFETY: same requirements as in GlobalAlloc::alloc_zeroed. + moto_rt::alloc::alloc_zeroed(layout) + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // SAFETY: same requirements as in GlobalAlloc::dealloc. + unsafe { moto_rt::alloc::dealloc(ptr, layout) } + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + // SAFETY: same requirements as in GlobalAlloc::realloc. + unsafe { moto_rt::alloc::realloc(ptr, layout, new_size) } + } +} diff --git a/library/std/src/sys/anonymous_pipe/mod.rs b/library/std/src/sys/anonymous_pipe/mod.rs index b6f464161ee2b..64b2c014b54fe 100644 --- a/library/std/src/sys/anonymous_pipe/mod.rs +++ b/library/std/src/sys/anonymous_pipe/mod.rs @@ -9,6 +9,10 @@ cfg_select! { mod windows; pub use windows::{AnonPipe, pipe}; } + target_os = "motor" => { + mod motor; + pub use motor::{AnonPipe, pipe}; + } _ => { mod unsupported; pub use unsupported::{AnonPipe, pipe}; diff --git a/library/std/src/sys/anonymous_pipe/motor.rs b/library/std/src/sys/anonymous_pipe/motor.rs new file mode 100644 index 0000000000000..dfe10f7fafe49 --- /dev/null +++ b/library/std/src/sys/anonymous_pipe/motor.rs @@ -0,0 +1,11 @@ +use crate::io; +use crate::sys::fd::FileDesc; +use crate::sys::pipe::anon_pipe; +use crate::sys_common::IntoInner; + +pub type AnonPipe = FileDesc; + +#[inline] +pub fn pipe() -> io::Result<(AnonPipe, AnonPipe)> { + anon_pipe().map(|(rx, wx)| (rx.into_inner(), wx.into_inner())) +} diff --git a/library/std/src/sys/args/mod.rs b/library/std/src/sys/args/mod.rs index 75c59da721e19..5424d40a15883 100644 --- a/library/std/src/sys/args/mod.rs +++ b/library/std/src/sys/args/mod.rs @@ -6,6 +6,7 @@ all(target_family = "unix", not(any(target_os = "espidf", target_os = "vita"))), target_family = "windows", target_os = "hermit", + target_os = "motor", target_os = "uefi", target_os = "wasi", target_os = "xous", @@ -28,6 +29,10 @@ cfg_select! { mod sgx; pub use sgx::*; } + target_os = "motor" => { + mod motor; + pub use motor::*; + } target_os = "uefi" => { mod uefi; pub use uefi::*; diff --git a/library/std/src/sys/args/motor.rs b/library/std/src/sys/args/motor.rs new file mode 100644 index 0000000000000..c3dbe87cec411 --- /dev/null +++ b/library/std/src/sys/args/motor.rs @@ -0,0 +1,13 @@ +pub use super::common::Args; +use crate::ffi::OsString; + +pub fn args() -> Args { + let motor_args: Vec = moto_rt::process::args(); + let mut rust_args = Vec::new(); + + for arg in motor_args { + rust_args.push(OsString::from(arg)); + } + + Args::new(rust_args) +} diff --git a/library/std/src/sys/env/mod.rs b/library/std/src/sys/env/mod.rs index f211a9fc86b3b..89856516b6dce 100644 --- a/library/std/src/sys/env/mod.rs +++ b/library/std/src/sys/env/mod.rs @@ -5,6 +5,7 @@ #[cfg(any( target_family = "unix", target_os = "hermit", + target_os = "motor", all(target_vendor = "fortanix", target_env = "sgx"), target_os = "solid_asp3", target_os = "uefi", @@ -26,6 +27,10 @@ cfg_select! { mod hermit; pub use hermit::*; } + target_os = "motor" => { + mod motor; + pub use motor::*; + } all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; pub use sgx::*; diff --git a/library/std/src/sys/env/motor.rs b/library/std/src/sys/env/motor.rs new file mode 100644 index 0000000000000..1f756ccd3ee85 --- /dev/null +++ b/library/std/src/sys/env/motor.rs @@ -0,0 +1,27 @@ +pub use super::common::Env; +use crate::ffi::{OsStr, OsString}; +use crate::io; +use crate::os::motor::ffi::OsStrExt; + +pub fn env() -> Env { + let motor_env: Vec<(String, String)> = moto_rt::process::env(); + let mut rust_env = vec![]; + + for (k, v) in motor_env { + rust_env.push((OsString::from(k), OsString::from(v))); + } + + Env::new(rust_env) +} + +pub fn getenv(key: &OsStr) -> Option { + moto_rt::process::getenv(key.as_str()).map(|s| OsString::from(s)) +} + +pub unsafe fn setenv(key: &OsStr, val: &OsStr) -> io::Result<()> { + Ok(moto_rt::process::setenv(key.as_str(), val.as_str())) +} + +pub unsafe fn unsetenv(key: &OsStr) -> io::Result<()> { + Ok(moto_rt::process::unsetenv(key.as_str())) +} diff --git a/library/std/src/sys/fd/mod.rs b/library/std/src/sys/fd/mod.rs index 7cb9dd1cba9d3..330499ecc18f6 100644 --- a/library/std/src/sys/fd/mod.rs +++ b/library/std/src/sys/fd/mod.rs @@ -11,6 +11,10 @@ cfg_select! { mod hermit; pub use hermit::*; } + target_os = "motor" => { + mod motor; + pub use motor::*; + } all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; pub use sgx::*; diff --git a/library/std/src/sys/fd/motor.rs b/library/std/src/sys/fd/motor.rs new file mode 100644 index 0000000000000..4211fef8007ab --- /dev/null +++ b/library/std/src/sys/fd/motor.rs @@ -0,0 +1,124 @@ +#![unstable(reason = "not public", issue = "none", feature = "fd")] + +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read}; +use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::sys::map_motor_error; +use crate::sys_common::{AsInner, FromInner, IntoInner}; + +#[derive(Debug)] +pub struct FileDesc(OwnedFd); + +impl FileDesc { + pub fn read(&self, buf: &mut [u8]) -> io::Result { + moto_rt::fs::read(self.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), cursor) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + io::default_read_vectored(|b| self.read(b), bufs) + } + + pub fn read_to_end(&self, buf: &mut Vec) -> io::Result { + let mut me = self; + (&mut me).read_to_end(buf) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + moto_rt::fs::write(self.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + crate::io::default_write_vectored(|b| self.write(b), bufs) + } + + pub fn is_write_vectored(&self) -> bool { + false + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + moto_rt::net::set_nonblocking(self.as_raw_fd(), nonblocking).map_err(map_motor_error) + } + + #[inline] + pub fn duplicate(&self) -> io::Result { + let fd = moto_rt::fs::duplicate(self.as_raw_fd()).map_err(map_motor_error)?; + // SAFETY: safe because we just got it from the OS runtime. + unsafe { Ok(Self::from_raw_fd(fd)) } + } + + #[inline] + pub fn try_clone(&self) -> io::Result { + self.duplicate() + } +} + +impl<'a> Read for &'a FileDesc { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (**self).read(buf) + } + + fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + (**self).read_buf(cursor) + } + + fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + (**self).read_vectored(bufs) + } + + #[inline] + fn is_read_vectored(&self) -> bool { + (**self).is_read_vectored() + } +} + +impl AsInner for FileDesc { + #[inline] + fn as_inner(&self) -> &OwnedFd { + &self.0 + } +} + +impl IntoInner for FileDesc { + fn into_inner(self) -> OwnedFd { + self.0 + } +} + +impl FromInner for FileDesc { + fn from_inner(owned_fd: OwnedFd) -> Self { + Self(owned_fd) + } +} + +impl AsFd for FileDesc { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl AsRawFd for FileDesc { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl IntoRawFd for FileDesc { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +impl FromRawFd for FileDesc { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + unsafe { Self(FromRawFd::from_raw_fd(raw_fd)) } + } +} diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 64f5a6b36d3db..403bfb2d9b929 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -27,6 +27,10 @@ cfg_select! { mod hermit; use hermit as imp; } + target_os = "motor" => { + mod motor; + use motor as imp; + } target_os = "solid_asp3" => { mod solid; use solid as imp; diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs new file mode 100644 index 0000000000000..656b6e81b9518 --- /dev/null +++ b/library/std/src/sys/fs/motor.rs @@ -0,0 +1,478 @@ +use crate::ffi::OsString; +use crate::hash::Hash; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; +use crate::path::{Path, PathBuf}; +use crate::sys::fd::FileDesc; +pub use crate::sys::fs::common::exists; +use crate::sys::time::SystemTime; +use crate::sys::{map_motor_error, unsupported}; +use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct FileType { + rt_filetype: u8, +} + +impl FileType { + pub fn is_dir(&self) -> bool { + self.rt_filetype == moto_rt::fs::FILETYPE_DIRECTORY + } + + pub fn is_file(&self) -> bool { + self.rt_filetype == moto_rt::fs::FILETYPE_FILE + } + + pub fn is_symlink(&self) -> bool { + false + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FilePermissions { + rt_perm: u64, +} + +impl FilePermissions { + pub fn readonly(&self) -> bool { + (self.rt_perm & moto_rt::fs::PERM_WRITE == 0) + && (self.rt_perm & moto_rt::fs::PERM_READ != 0) + } + + pub fn set_readonly(&mut self, readonly: bool) { + if readonly { + self.rt_perm = moto_rt::fs::PERM_READ; + } else { + self.rt_perm = moto_rt::fs::PERM_READ | moto_rt::fs::PERM_WRITE; + } + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct FileTimes { + modified: u128, + accessed: u128, +} + +impl FileTimes { + pub fn set_accessed(&mut self, t: SystemTime) { + self.accessed = t.as_u128(); + } + + pub fn set_modified(&mut self, t: SystemTime) { + self.modified = t.as_u128(); + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct FileAttr { + inner: moto_rt::fs::FileAttr, +} + +impl FileAttr { + pub fn size(&self) -> u64 { + self.inner.size + } + + pub fn perm(&self) -> FilePermissions { + FilePermissions { rt_perm: self.inner.perm } + } + + pub fn file_type(&self) -> FileType { + FileType { rt_filetype: self.inner.file_type } + } + + pub fn modified(&self) -> io::Result { + match self.inner.modified { + 0 => Err(crate::io::Error::from(crate::io::ErrorKind::Other)), + x => Ok(SystemTime::from_u128(x)), + } + } + + pub fn accessed(&self) -> io::Result { + match self.inner.accessed { + 0 => Err(crate::io::Error::from(crate::io::ErrorKind::Other)), + x => Ok(SystemTime::from_u128(x)), + } + } + + pub fn created(&self) -> io::Result { + match self.inner.created { + 0 => Err(crate::io::Error::from(crate::io::ErrorKind::Other)), + x => Ok(SystemTime::from_u128(x)), + } + } +} + +#[derive(Clone, Debug)] +pub struct OpenOptions { + rt_open_options: u32, +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + OpenOptions { rt_open_options: 0 } + } + + pub fn read(&mut self, read: bool) { + if read { + self.rt_open_options |= moto_rt::fs::O_READ; + } else { + self.rt_open_options &= !moto_rt::fs::O_READ; + } + } + + pub fn write(&mut self, write: bool) { + if write { + self.rt_open_options |= moto_rt::fs::O_WRITE; + } else { + self.rt_open_options &= !moto_rt::fs::O_WRITE; + } + } + + pub fn append(&mut self, append: bool) { + if append { + self.rt_open_options |= moto_rt::fs::O_APPEND; + } else { + self.rt_open_options &= !moto_rt::fs::O_APPEND; + } + } + + pub fn truncate(&mut self, truncate: bool) { + if truncate { + self.rt_open_options |= moto_rt::fs::O_TRUNCATE; + } else { + self.rt_open_options &= !moto_rt::fs::O_TRUNCATE; + } + } + + pub fn create(&mut self, create: bool) { + if create { + self.rt_open_options |= moto_rt::fs::O_CREATE; + } else { + self.rt_open_options &= !moto_rt::fs::O_CREATE; + } + } + + pub fn create_new(&mut self, create_new: bool) { + if create_new { + self.rt_open_options |= moto_rt::fs::O_CREATE_NEW; + } else { + self.rt_open_options &= !moto_rt::fs::O_CREATE_NEW; + } + } +} + +#[derive(Debug)] +pub struct File(FileDesc); + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + moto_rt::fs::open(path, opts.rt_open_options) + .map(|fd| unsafe { Self::from_raw_fd(fd) }) + .map_err(map_motor_error) + } + + pub fn file_attr(&self) -> io::Result { + moto_rt::fs::get_file_attr(self.as_raw_fd()) + .map(|inner| -> FileAttr { FileAttr { inner } }) + .map_err(map_motor_error) + } + + pub fn fsync(&self) -> io::Result<()> { + moto_rt::fs::fsync(self.as_raw_fd()).map_err(map_motor_error) + } + + pub fn datasync(&self) -> io::Result<()> { + moto_rt::fs::datasync(self.as_raw_fd()).map_err(map_motor_error) + } + + pub fn truncate(&self, size: u64) -> io::Result<()> { + moto_rt::fs::truncate(self.as_raw_fd(), size).map_err(map_motor_error) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + moto_rt::fs::read(self.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + crate::io::default_read_vectored(|b| self.read(b), bufs) + } + + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), cursor) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + moto_rt::fs::write(self.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + crate::io::default_write_vectored(|b| self.write(b), bufs) + } + + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn flush(&self) -> io::Result<()> { + moto_rt::fs::flush(self.as_raw_fd()).map_err(map_motor_error) + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result { + match pos { + SeekFrom::Start(offset) => { + moto_rt::fs::seek(self.as_raw_fd(), offset as i64, moto_rt::fs::SEEK_SET) + .map_err(map_motor_error) + } + SeekFrom::End(offset) => { + moto_rt::fs::seek(self.as_raw_fd(), offset, moto_rt::fs::SEEK_END) + .map_err(map_motor_error) + } + SeekFrom::Current(offset) => { + moto_rt::fs::seek(self.as_raw_fd(), offset, moto_rt::fs::SEEK_CUR) + .map_err(map_motor_error) + } + } + } + + pub fn tell(&self) -> io::Result { + self.seek(SeekFrom::Current(0)) + } + + pub fn duplicate(&self) -> io::Result { + moto_rt::fs::duplicate(self.as_raw_fd()) + .map(|fd| unsafe { Self::from_raw_fd(fd) }) + .map_err(map_motor_error) + } + + pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> { + moto_rt::fs::set_file_perm(self.as_raw_fd(), perm.rt_perm).map_err(map_motor_error) + } + + pub fn set_times(&self, _times: FileTimes) -> io::Result<()> { + unsupported() // Let's not do that. + } + + pub fn lock(&self) -> io::Result<()> { + unsupported() + } + + pub fn lock_shared(&self) -> io::Result<()> { + unsupported() + } + + pub fn try_lock(&self) -> Result<(), crate::fs::TryLockError> { + Err(crate::fs::TryLockError::Error(io::Error::from(io::ErrorKind::Unsupported))) + } + + pub fn try_lock_shared(&self) -> Result<(), crate::fs::TryLockError> { + Err(crate::fs::TryLockError::Error(io::Error::from(io::ErrorKind::Unsupported))) + } + + pub fn unlock(&self) -> io::Result<()> { + unsupported() + } + + pub fn size(&self) -> Option> { + None + } +} + +#[derive(Debug)] +pub struct DirBuilder {} + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder {} + } + + pub fn mkdir(&self, path: &Path) -> io::Result<()> { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + moto_rt::fs::mkdir(path).map_err(map_motor_error) + } +} + +pub fn unlink(path: &Path) -> io::Result<()> { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + moto_rt::fs::unlink(path).map_err(map_motor_error) +} + +pub fn rename(old: &Path, new: &Path) -> io::Result<()> { + let old = old.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + let new = new.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + moto_rt::fs::rename(old, new).map_err(map_motor_error) +} + +pub fn rmdir(path: &Path) -> io::Result<()> { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + moto_rt::fs::rmdir(path).map_err(map_motor_error) +} + +pub fn remove_dir_all(path: &Path) -> io::Result<()> { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + moto_rt::fs::rmdir_all(path).map_err(map_motor_error) +} + +pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + moto_rt::fs::set_perm(path, perm.rt_perm).map_err(map_motor_error) +} + +pub fn readlink(_p: &Path) -> io::Result { + unsupported() +} + +pub fn symlink(_original: &Path, _link: &Path) -> io::Result<()> { + unsupported() +} + +pub fn link(_src: &Path, _dst: &Path) -> io::Result<()> { + unsupported() +} + +pub fn stat(path: &Path) -> io::Result { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + let inner = moto_rt::fs::stat(path).map_err(map_motor_error)?; + Ok(FileAttr { inner }) +} + +pub fn lstat(path: &Path) -> io::Result { + stat(path) +} + +pub fn canonicalize(path: &Path) -> io::Result { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + let path = moto_rt::fs::canonicalize(path).map_err(map_motor_error)?; + Ok(path.into()) +} + +pub fn copy(from: &Path, to: &Path) -> io::Result { + let from = from.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + let to = to.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + moto_rt::fs::copy(from, to).map_err(map_motor_error) +} + +#[derive(Debug)] +pub struct ReadDir { + rt_fd: moto_rt::RtFd, + path: String, +} + +impl Drop for ReadDir { + fn drop(&mut self) { + moto_rt::fs::closedir(self.rt_fd).unwrap(); + } +} + +pub fn readdir(path: &Path) -> io::Result { + let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; + Ok(ReadDir { + rt_fd: moto_rt::fs::opendir(path).map_err(map_motor_error)?, + path: path.to_owned(), + }) +} + +impl Iterator for ReadDir { + type Item = io::Result; + + fn next(&mut self) -> Option> { + match moto_rt::fs::readdir(self.rt_fd).map_err(map_motor_error) { + Ok(maybe_item) => match maybe_item { + Some(inner) => Some(Ok(DirEntry { inner, parent_path: self.path.clone() })), + None => None, + }, + Err(err) => Some(Err(err)), + } + } +} + +pub struct DirEntry { + parent_path: String, + inner: moto_rt::fs::DirEntry, +} + +impl DirEntry { + fn filename(&self) -> &str { + core::str::from_utf8(unsafe { + core::slice::from_raw_parts(self.inner.fname.as_ptr(), self.inner.fname_size as usize) + }) + .unwrap() + } + + pub fn path(&self) -> PathBuf { + let mut path = self.parent_path.clone(); + path.push_str("/"); + path.push_str(self.filename()); + path.into() + } + + pub fn file_name(&self) -> OsString { + self.filename().to_owned().into() + } + + pub fn metadata(&self) -> io::Result { + Ok(FileAttr { inner: self.inner.attr }) + } + + pub fn file_type(&self) -> io::Result { + Ok(FileType { rt_filetype: self.inner.attr.file_type }) + } +} + +impl AsInner for File { + #[inline] + fn as_inner(&self) -> &FileDesc { + &self.0 + } +} + +impl AsInnerMut for File { + #[inline] + fn as_inner_mut(&mut self) -> &mut FileDesc { + &mut self.0 + } +} + +impl IntoInner for File { + fn into_inner(self) -> FileDesc { + self.0 + } +} + +impl FromInner for File { + fn from_inner(file_desc: FileDesc) -> Self { + Self(file_desc) + } +} + +impl AsFd for File { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl AsRawFd for File { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl IntoRawFd for File { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +impl FromRawFd for File { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + unsafe { Self(FromRawFd::from_raw_fd(raw_fd)) } + } +} diff --git a/library/std/src/sys/io/is_terminal/motor.rs b/library/std/src/sys/io/is_terminal/motor.rs new file mode 100644 index 0000000000000..0b70299adaaa2 --- /dev/null +++ b/library/std/src/sys/io/is_terminal/motor.rs @@ -0,0 +1,6 @@ +use crate::os::fd::{AsFd, AsRawFd}; + +pub fn is_terminal(fd: &impl AsFd) -> bool { + let fd = fd.as_fd(); + moto_rt::fs::is_terminal(fd.as_raw_fd()) +} diff --git a/library/std/src/sys/io/mod.rs b/library/std/src/sys/io/mod.rs index fe8ec1dbb7325..0916eda1c06a5 100644 --- a/library/std/src/sys/io/mod.rs +++ b/library/std/src/sys/io/mod.rs @@ -39,6 +39,10 @@ mod is_terminal { mod hermit; pub use hermit::*; } + target_os = "motor" => { + mod motor; + pub use motor::*; + } _ => { mod unsupported; pub use unsupported::*; diff --git a/library/std/src/sys/net/connection/mod.rs b/library/std/src/sys/net/connection/mod.rs index 41e7159f909ae..2f064914a8317 100644 --- a/library/std/src/sys/net/connection/mod.rs +++ b/library/std/src/sys/net/connection/mod.rs @@ -17,6 +17,10 @@ cfg_select! { mod wasip1; pub use wasip1::*; } + target_os = "motor" => { + mod motor; + pub use motor::*; + } target_os = "xous" => { mod xous; pub use xous::*; diff --git a/library/std/src/sys/net/connection/motor.rs b/library/std/src/sys/net/connection/motor.rs new file mode 100644 index 0000000000000..e9bf29e34f90c --- /dev/null +++ b/library/std/src/sys/net/connection/motor.rs @@ -0,0 +1,521 @@ +pub use moto_rt::netc; + +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::net::SocketAddr::{V4, V6}; +use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}; +use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; +use crate::sys::fd::FileDesc; +use crate::sys::map_motor_error; +use crate::sys_common::{AsInner, FromInner, IntoInner}; +use crate::time::Duration; + +// We want to re-use as much of Rust's stdlib code as possible, +// and most of it is unixy, but with a lot of nesting. +#[derive(Debug)] +pub struct Socket(FileDesc); + +#[derive(Debug)] +pub struct TcpStream { + inner: Socket, +} + +impl TcpStream { + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } + + pub fn connect(addr: A) -> io::Result { + let addr = into_netc(&addr.to_socket_addrs()?.next().unwrap()); + moto_rt::net::tcp_connect(&addr, Duration::MAX, false) + .map(|fd| Self { inner: unsafe { Socket::from_raw_fd(fd) } }) + .map_err(map_motor_error) + } + + pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result { + let addr = into_netc(addr); + moto_rt::net::tcp_connect(&addr, timeout, false) + .map(|fd| Self { inner: unsafe { Socket::from_raw_fd(fd) } }) + .map_err(map_motor_error) + } + + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + moto_rt::net::set_read_timeout(self.inner.as_raw_fd(), timeout).map_err(map_motor_error) + } + + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + moto_rt::net::set_write_timeout(self.inner.as_raw_fd(), timeout).map_err(map_motor_error) + } + + pub fn read_timeout(&self) -> io::Result> { + moto_rt::net::read_timeout(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn write_timeout(&self) -> io::Result> { + moto_rt::net::write_timeout(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + moto_rt::net::peek(self.inner.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + moto_rt::fs::read(self.inner.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), cursor) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + let bufs: &mut [&mut [u8]] = unsafe { core::mem::transmute(bufs) }; + moto_rt::fs::read_vectored(self.inner.as_raw_fd(), bufs).map_err(map_motor_error) + } + + pub fn is_read_vectored(&self) -> bool { + true + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + moto_rt::fs::write(self.inner.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + let bufs: &[&[u8]] = unsafe { core::mem::transmute(bufs) }; + moto_rt::fs::write_vectored(self.inner.as_raw_fd(), bufs).map_err(map_motor_error) + } + + pub fn is_write_vectored(&self) -> bool { + true + } + + pub fn peer_addr(&self) -> io::Result { + moto_rt::net::peer_addr(self.inner.as_raw_fd()) + .map(|addr| from_netc(&addr)) + .map_err(map_motor_error) + } + + pub fn socket_addr(&self) -> io::Result { + moto_rt::net::socket_addr(self.inner.as_raw_fd()) + .map(|addr| from_netc(&addr)) + .map_err(map_motor_error) + } + + pub fn shutdown(&self, shutdown: Shutdown) -> io::Result<()> { + let shutdown = match shutdown { + Shutdown::Read => moto_rt::net::SHUTDOWN_READ, + Shutdown::Write => moto_rt::net::SHUTDOWN_WRITE, + Shutdown::Both => moto_rt::net::SHUTDOWN_READ | moto_rt::net::SHUTDOWN_WRITE, + }; + + moto_rt::net::shutdown(self.inner.as_raw_fd(), shutdown).map_err(map_motor_error) + } + + pub fn duplicate(&self) -> io::Result { + moto_rt::fs::duplicate(self.inner.as_raw_fd()) + .map(|fd| Self { inner: unsafe { Socket::from_raw_fd(fd) } }) + .map_err(map_motor_error) + } + + pub fn set_linger(&self, timeout: Option) -> io::Result<()> { + moto_rt::net::set_linger(self.inner.as_raw_fd(), timeout).map_err(map_motor_error) + } + + pub fn linger(&self) -> io::Result> { + moto_rt::net::linger(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { + moto_rt::net::set_nodelay(self.inner.as_raw_fd(), nodelay).map_err(map_motor_error) + } + + pub fn nodelay(&self) -> io::Result { + moto_rt::net::nodelay(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + moto_rt::net::set_ttl(self.inner.as_raw_fd(), ttl).map_err(map_motor_error) + } + + pub fn ttl(&self) -> io::Result { + moto_rt::net::ttl(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn take_error(&self) -> io::Result> { + let e = moto_rt::net::take_error(self.inner.as_raw_fd()).map_err(map_motor_error)?; + if e == moto_rt::E_OK { Ok(None) } else { Ok(Some(map_motor_error(e))) } + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + moto_rt::net::set_nonblocking(self.inner.as_raw_fd(), nonblocking).map_err(map_motor_error) + } +} + +#[derive(Debug)] +pub struct TcpListener { + inner: Socket, +} + +impl TcpListener { + #[inline] + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } + + pub fn bind(addr: A) -> io::Result { + let addr = into_netc(&addr.to_socket_addrs()?.next().unwrap()); + moto_rt::net::bind(moto_rt::net::PROTO_TCP, &addr) + .map(|fd| Self { inner: unsafe { Socket::from_raw_fd(fd) } }) + .map_err(map_motor_error) + } + + pub fn socket_addr(&self) -> io::Result { + moto_rt::net::socket_addr(self.inner.as_raw_fd()) + .map(|addr| from_netc(&addr)) + .map_err(map_motor_error) + } + + pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { + moto_rt::net::accept(self.inner.as_raw_fd()) + .map(|(fd, addr)| { + (TcpStream { inner: unsafe { Socket::from_raw_fd(fd) } }, from_netc(&addr)) + }) + .map_err(map_motor_error) + } + + pub fn duplicate(&self) -> io::Result { + moto_rt::fs::duplicate(self.inner.as_raw_fd()) + .map(|fd| Self { inner: unsafe { Socket::from_raw_fd(fd) } }) + .map_err(map_motor_error) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + moto_rt::net::set_ttl(self.inner.as_raw_fd(), ttl).map_err(map_motor_error) + } + + pub fn ttl(&self) -> io::Result { + moto_rt::net::ttl(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn set_only_v6(&self, only_v6: bool) -> io::Result<()> { + moto_rt::net::set_only_v6(self.inner.as_raw_fd(), only_v6).map_err(map_motor_error) + } + + pub fn only_v6(&self) -> io::Result { + moto_rt::net::only_v6(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn take_error(&self) -> io::Result> { + let e = moto_rt::net::take_error(self.inner.as_raw_fd()).map_err(map_motor_error)?; + if e == moto_rt::E_OK { Ok(None) } else { Ok(Some(map_motor_error(e))) } + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + moto_rt::net::set_nonblocking(self.inner.as_raw_fd(), nonblocking).map_err(map_motor_error) + } +} + +#[derive(Debug)] +pub struct UdpSocket { + inner: Socket, +} + +impl UdpSocket { + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } + + pub fn bind(addr: A) -> io::Result { + let addr = into_netc(&addr.to_socket_addrs()?.next().unwrap()); + moto_rt::net::bind(moto_rt::net::PROTO_UDP, &addr) + .map(|fd| Self { inner: unsafe { Socket::from_raw_fd(fd) } }) + .map_err(map_motor_error) + } + + pub fn peer_addr(&self) -> io::Result { + moto_rt::net::peer_addr(self.inner.as_raw_fd()) + .map(|addr| from_netc(&addr)) + .map_err(map_motor_error) + } + + pub fn socket_addr(&self) -> io::Result { + moto_rt::net::socket_addr(self.inner.as_raw_fd()) + .map(|addr| from_netc(&addr)) + .map_err(map_motor_error) + } + + pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + moto_rt::net::udp_recv_from(self.inner.as_raw_fd(), buf) + .map(|(sz, addr)| (sz, from_netc(&addr))) + .map_err(map_motor_error) + } + + pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + moto_rt::net::udp_peek_from(self.inner.as_raw_fd(), buf) + .map(|(sz, addr)| (sz, from_netc(&addr))) + .map_err(map_motor_error) + } + + pub fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> io::Result { + let addr = into_netc(addr); + moto_rt::net::udp_send_to(self.inner.as_raw_fd(), buf, &addr).map_err(map_motor_error) + } + + pub fn duplicate(&self) -> io::Result { + moto_rt::fs::duplicate(self.inner.as_raw_fd()) + .map(|fd| Self { inner: unsafe { Socket::from_raw_fd(fd) } }) + .map_err(map_motor_error) + } + + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + moto_rt::net::set_read_timeout(self.inner.as_raw_fd(), timeout).map_err(map_motor_error) + } + + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + moto_rt::net::set_write_timeout(self.inner.as_raw_fd(), timeout).map_err(map_motor_error) + } + + pub fn read_timeout(&self) -> io::Result> { + moto_rt::net::read_timeout(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn write_timeout(&self) -> io::Result> { + moto_rt::net::write_timeout(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn set_broadcast(&self, broadcast: bool) -> io::Result<()> { + moto_rt::net::set_udp_broadcast(self.inner.as_raw_fd(), broadcast).map_err(map_motor_error) + } + + pub fn broadcast(&self) -> io::Result { + moto_rt::net::udp_broadcast(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn set_multicast_loop_v4(&self, val: bool) -> io::Result<()> { + moto_rt::net::set_udp_multicast_loop_v4(self.inner.as_raw_fd(), val) + .map_err(map_motor_error) + } + + pub fn multicast_loop_v4(&self) -> io::Result { + moto_rt::net::udp_multicast_loop_v4(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn set_multicast_ttl_v4(&self, val: u32) -> io::Result<()> { + moto_rt::net::set_udp_multicast_ttl_v4(self.inner.as_raw_fd(), val).map_err(map_motor_error) + } + + pub fn multicast_ttl_v4(&self) -> io::Result { + moto_rt::net::udp_multicast_ttl_v4(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn set_multicast_loop_v6(&self, val: bool) -> io::Result<()> { + moto_rt::net::set_udp_multicast_loop_v6(self.inner.as_raw_fd(), val) + .map_err(map_motor_error) + } + + pub fn multicast_loop_v6(&self) -> io::Result { + moto_rt::net::udp_multicast_loop_v6(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn join_multicast_v4(&self, addr: &Ipv4Addr, iface: &Ipv4Addr) -> io::Result<()> { + let addr = (*addr).into(); + let iface = (*iface).into(); + moto_rt::net::join_udp_multicast_v4(self.inner.as_raw_fd(), &addr, &iface) + .map_err(map_motor_error) + } + + pub fn join_multicast_v6(&self, addr: &Ipv6Addr, iface: u32) -> io::Result<()> { + let addr = (*addr).into(); + moto_rt::net::join_udp_multicast_v6(self.inner.as_raw_fd(), &addr, iface) + .map_err(map_motor_error) + } + + pub fn leave_multicast_v4(&self, addr: &Ipv4Addr, iface: &Ipv4Addr) -> io::Result<()> { + let addr = (*addr).into(); + let iface = (*iface).into(); + moto_rt::net::leave_udp_multicast_v4(self.inner.as_raw_fd(), &addr, &iface) + .map_err(map_motor_error) + } + + pub fn leave_multicast_v6(&self, addr: &Ipv6Addr, iface: u32) -> io::Result<()> { + let addr = (*addr).into(); + moto_rt::net::leave_udp_multicast_v6(self.inner.as_raw_fd(), &addr, iface) + .map_err(map_motor_error) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + moto_rt::net::set_ttl(self.inner.as_raw_fd(), ttl).map_err(map_motor_error) + } + + pub fn ttl(&self) -> io::Result { + moto_rt::net::ttl(self.inner.as_raw_fd()).map_err(map_motor_error) + } + + pub fn take_error(&self) -> io::Result> { + moto_rt::net::take_error(self.inner.as_raw_fd()) + .map(|e| match e { + moto_rt::E_OK => None, + e => Some(map_motor_error(e)), + }) + .map_err(map_motor_error) + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + moto_rt::net::set_nonblocking(self.inner.as_raw_fd(), nonblocking).map_err(map_motor_error) + } + + pub fn recv(&self, buf: &mut [u8]) -> io::Result { + moto_rt::fs::read(self.inner.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + moto_rt::net::peek(self.inner.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn send(&self, buf: &[u8]) -> io::Result { + moto_rt::fs::write(self.inner.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn connect(&self, addr: A) -> io::Result<()> { + let addr = into_netc(&addr.to_socket_addrs()?.next().unwrap()); + moto_rt::net::udp_connect(self.inner.as_raw_fd(), &addr).map_err(map_motor_error) + } +} + +pub struct LookupHost { + addresses: alloc::collections::VecDeque, +} + +pub fn lookup_host(host: &str, port: u16) -> io::Result { + let (_port, addresses) = moto_rt::net::lookup_host(host, port).map_err(map_motor_error)?; + Ok(LookupHost { addresses }) +} + +impl Iterator for LookupHost { + type Item = SocketAddr; + fn next(&mut self) -> Option { + self.addresses.pop_front().map(|addr| from_netc(&addr)) + } +} + +impl TryFrom<&str> for LookupHost { + type Error = io::Error; + + fn try_from(host_port: &str) -> io::Result { + let (host, port_str) = host_port + .rsplit_once(':') + .ok_or(moto_rt::E_INVALID_ARGUMENT) + .map_err(map_motor_error)?; + let port: u16 = + port_str.parse().map_err(|_| moto_rt::E_INVALID_ARGUMENT).map_err(map_motor_error)?; + (host, port).try_into() + } +} + +impl<'a> TryFrom<(&'a str, u16)> for LookupHost { + type Error = io::Error; + + fn try_from(host_port: (&'a str, u16)) -> io::Result { + let (host, port) = host_port; + + let (_port, addresses) = moto_rt::net::lookup_host(host, port).map_err(map_motor_error)?; + Ok(LookupHost { addresses }) + } +} + +fn into_netc(addr: &SocketAddr) -> netc::sockaddr { + match addr { + V4(addr4) => netc::sockaddr { v4: (*addr4).into() }, + V6(addr6) => netc::sockaddr { v6: (*addr6).into() }, + } +} + +fn from_netc(addr: &netc::sockaddr) -> SocketAddr { + // SAFETY: all variants of union netc::sockaddr have `sin_family` at the same offset. + let family = unsafe { addr.v4.sin_family }; + match family { + netc::AF_INET => SocketAddr::V4(crate::net::SocketAddrV4::from(unsafe { addr.v4 })), + netc::AF_INET6 => SocketAddr::V6(crate::net::SocketAddrV6::from(unsafe { addr.v6 })), + _ => panic!("bad sin_family {family}"), + } +} + +impl AsInner for Socket { + #[inline] + fn as_inner(&self) -> &FileDesc { + &self.0 + } +} + +impl IntoInner for Socket { + fn into_inner(self) -> FileDesc { + self.0 + } +} + +impl FromInner for Socket { + fn from_inner(file_desc: FileDesc) -> Self { + Self(file_desc) + } +} + +impl AsFd for Socket { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl AsRawFd for Socket { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl IntoRawFd for Socket { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +impl FromRawFd for Socket { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + Self(FromRawFd::from_raw_fd(raw_fd)) + } +} + +impl AsInner for TcpStream { + #[inline] + fn as_inner(&self) -> &Socket { + &self.inner + } +} + +impl FromInner for TcpStream { + fn from_inner(socket: Socket) -> TcpStream { + TcpStream { inner: socket } + } +} + +impl FromInner for TcpListener { + fn from_inner(socket: Socket) -> TcpListener { + TcpListener { inner: socket } + } +} + +impl FromInner for UdpSocket { + fn from_inner(socket: Socket) -> UdpSocket { + UdpSocket { inner: socket } + } +} diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs index 9e964540a87c1..e11df38a8ee68 100644 --- a/library/std/src/sys/pal/mod.rs +++ b/library/std/src/sys/pal/mod.rs @@ -41,6 +41,10 @@ cfg_select! { mod hermit; pub use self::hermit::*; } + target_os = "motor" => { + mod motor; + pub use self::motor::*; + } target_os = "trusty" => { mod trusty; pub use self::trusty::*; diff --git a/library/std/src/sys/pal/motor/mod.rs b/library/std/src/sys/pal/motor/mod.rs new file mode 100644 index 0000000000000..c64f8ff7a8a83 --- /dev/null +++ b/library/std/src/sys/pal/motor/mod.rs @@ -0,0 +1,77 @@ +#![allow(unsafe_op_in_unsafe_fn)] + +pub mod os; +pub mod pipe; +pub mod time; + +pub use moto_rt::futex; + +use crate::io as std_io; +use crate::sys::RawOsError; + +pub(crate) fn map_motor_error(err: moto_rt::ErrorCode) -> crate::io::Error { + crate::io::Error::from_raw_os_error(err.into()) +} + +#[cfg(not(test))] +#[unsafe(no_mangle)] +pub extern "C" fn motor_start() -> ! { + // Initialize the runtime. + moto_rt::start(); + + // Call main. + unsafe extern "C" { + fn main(_: isize, _: *const *const u8, _: u8) -> i32; + } + let result = unsafe { main(0, core::ptr::null(), 0) }; + + // Terminate the process. + moto_rt::process::exit(result) +} + +// SAFETY: must be called only once during runtime initialization. +// NOTE: Motor OS uses moto_rt::start() to initialize runtime (see above). +pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} + +// SAFETY: must be called only once during runtime cleanup. +// NOTE: this is not guaranteed to run, for example when the program aborts. +pub unsafe fn cleanup() {} + +pub fn unsupported() -> std_io::Result { + Err(unsupported_err()) +} + +pub fn unsupported_err() -> std_io::Error { + std_io::Error::UNSUPPORTED_PLATFORM +} + +pub fn is_interrupted(_code: RawOsError) -> bool { + false // Motor OS doesn't have signals. +} + +pub fn decode_error_kind(code: RawOsError) -> crate::io::ErrorKind { + use moto_rt::error::*; + use std_io::ErrorKind; + + if code < 0 || code > u16::MAX.into() { + return std_io::ErrorKind::Uncategorized; + } + + match code as moto_rt::ErrorCode /* u16 */ { + E_ALREADY_IN_USE => ErrorKind::AlreadyExists, + E_INVALID_FILENAME => ErrorKind::InvalidFilename, + E_NOT_FOUND => ErrorKind::NotFound, + E_TIMED_OUT => ErrorKind::TimedOut, + E_NOT_IMPLEMENTED => ErrorKind::Unsupported, + E_FILE_TOO_LARGE => ErrorKind::FileTooLarge, + E_UNEXPECTED_EOF => ErrorKind::UnexpectedEof, + E_INVALID_ARGUMENT => ErrorKind::InvalidInput, + E_NOT_READY => ErrorKind::WouldBlock, + E_NOT_CONNECTED => ErrorKind::NotConnected, + _ => crate::io::ErrorKind::Uncategorized, + } +} + +pub fn abort_internal() -> ! { + core::intrinsics::abort(); +} diff --git a/library/std/src/sys/pal/motor/os.rs b/library/std/src/sys/pal/motor/os.rs new file mode 100644 index 0000000000000..052e3b238b6af --- /dev/null +++ b/library/std/src/sys/pal/motor/os.rs @@ -0,0 +1,100 @@ +use super::map_motor_error; +use crate::error::Error as StdError; +use crate::ffi::{OsStr, OsString}; +use crate::marker::PhantomData; +use crate::os::motor::ffi::OsStrExt; +use crate::path::{self, PathBuf}; +use crate::sys::RawOsError; +use crate::{fmt, io}; + +pub fn errno() -> RawOsError { + // Not used in Motor OS because it is ambiguous: Motor OS + // is micro-kernel-based, and I/O happens via a shared-memory + // ring buffer, so an I/O operation that on a unix is a syscall + // may involve no sycalls on Motor OS at all, or a syscall + // that e.g. waits for a notification from the I/O driver + // (sys-io); and the wait syscall may succeed, but the + // driver may report an I/O error; or a bunch of results + // for several I/O operations, some successful and some + // not. + // + // Also I/O operations in a Motor OS process are handled by a + // separate runtime background/I/O thread, so it is really hard + // to define what "last system error in the current thread" + // actually means. + moto_rt::E_UNKNOWN.into() +} + +pub fn error_string(errno: RawOsError) -> String { + let error_code: moto_rt::ErrorCode = match errno { + x if x < 0 => moto_rt::E_UNKNOWN, + x if x > u16::MAX.into() => moto_rt::E_UNKNOWN, + x => x as moto_rt::ErrorCode, /* u16 */ + }; + format!("{}", moto_rt::Error::from(error_code)) +} + +pub fn getcwd() -> io::Result { + moto_rt::fs::getcwd().map(PathBuf::from).map_err(map_motor_error) +} + +pub fn chdir(path: &path::Path) -> io::Result<()> { + moto_rt::fs::chdir(path.as_os_str().as_str()).map_err(map_motor_error) +} + +pub struct SplitPaths<'a>(!, PhantomData<&'a ()>); + +pub fn split_paths(_unparsed: &OsStr) -> SplitPaths<'_> { + panic!("unsupported") +} + +impl<'a> Iterator for SplitPaths<'a> { + type Item = PathBuf; + fn next(&mut self) -> Option { + self.0 + } +} + +#[derive(Debug)] +pub struct JoinPathsError; + +pub fn join_paths(_paths: I) -> Result +where + I: Iterator, + T: AsRef, +{ + Err(JoinPathsError) +} + +impl fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "not supported on this platform yet".fmt(f) + } +} + +impl StdError for JoinPathsError { + #[allow(deprecated)] + fn description(&self) -> &str { + "not supported on this platform yet" + } +} + +pub fn current_exe() -> io::Result { + moto_rt::process::current_exe().map(PathBuf::from).map_err(map_motor_error) +} + +pub fn temp_dir() -> PathBuf { + PathBuf::from(moto_rt::fs::TEMP_DIR) +} + +pub fn home_dir() -> Option { + None +} + +pub fn exit(code: i32) -> ! { + moto_rt::process::exit(code) +} + +pub fn getpid() -> u32 { + panic!("Pids on Motor OS are u64.") +} diff --git a/library/std/src/sys/pal/motor/pipe.rs b/library/std/src/sys/pal/motor/pipe.rs new file mode 100644 index 0000000000000..d3be6ddf1573e --- /dev/null +++ b/library/std/src/sys/pal/motor/pipe.rs @@ -0,0 +1,121 @@ +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::sys::fd::FileDesc; +use crate::sys::map_motor_error; +use crate::sys_common::{FromInner, IntoInner}; + +#[derive(Debug)] +pub struct AnonPipe(FileDesc); + +impl From for AnonPipe { + fn from(rt_fd: moto_rt::RtFd) -> AnonPipe { + unsafe { AnonPipe::from_raw_fd(rt_fd) } + } +} + +impl AnonPipe { + pub fn read(&self, buf: &mut [u8]) -> io::Result { + moto_rt::fs::read(self.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), cursor) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + crate::io::default_read_vectored(|b| self.read(b), bufs) + } + + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + moto_rt::fs::write(self.as_raw_fd(), buf).map_err(map_motor_error) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + crate::io::default_write_vectored(|b| self.write(b), bufs) + } + + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn read_to_end(&self, buf: &mut Vec) -> io::Result { + let mut temp_vec = Vec::new(); + let mut size = 0_usize; + loop { + temp_vec.resize(256, 0_u8); + match self.read(&mut temp_vec[..]) { + Ok(sz) => { + if sz == 0 { + return Ok(size); + } + size += sz; + temp_vec.truncate(sz); + buf.append(&mut temp_vec); + } + Err(err) => { + if size != 0 { + return Ok(size); + } else { + return Err(err); + } + } + } + } + } +} + +impl AsRawFd for AnonPipe { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl FromRawFd for AnonPipe { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + let desc = FileDesc::from_raw_fd(fd); + Self(desc) + } +} + +impl IntoRawFd for AnonPipe { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +impl AsFd for AnonPipe { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl IntoInner for AnonPipe { + fn into_inner(self) -> OwnedFd { + self.0.into_inner() + } +} + +impl IntoInner for AnonPipe { + fn into_inner(self) -> FileDesc { + self.0 + } +} + +impl FromInner for AnonPipe { + fn from_inner(owned_fd: OwnedFd) -> Self { + Self(FileDesc::from_inner(owned_fd)) + } +} + +pub fn read2(_p1: AnonPipe, _v1: &mut Vec, _p2: AnonPipe, _v2: &mut Vec) -> io::Result<()> { + Err(io::Error::from_raw_os_error(moto_rt::E_NOT_IMPLEMENTED.into())) +} + +#[inline] +pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { + Err(io::Error::UNSUPPORTED_PLATFORM) +} diff --git a/library/std/src/sys/pal/motor/time.rs b/library/std/src/sys/pal/motor/time.rs new file mode 100644 index 0000000000000..e917fd466c2e4 --- /dev/null +++ b/library/std/src/sys/pal/motor/time.rs @@ -0,0 +1 @@ +pub use moto_rt::time::{Instant, SystemTime, UNIX_EPOCH}; diff --git a/library/std/src/sys/path/unix.rs b/library/std/src/sys/path/unix.rs index faa2616a6320d..15530323a198d 100644 --- a/library/std/src/sys/path/unix.rs +++ b/library/std/src/sys/path/unix.rs @@ -62,7 +62,7 @@ pub(crate) fn absolute(path: &Path) -> io::Result { } pub(crate) fn is_absolute(path: &Path) -> bool { - if cfg!(any(unix, target_os = "hermit", target_os = "wasi")) { + if cfg!(any(unix, target_os = "hermit", target_os = "wasi", target_os = "motor")) { path.has_root() } else { path.has_root() && path.prefix().is_some() diff --git a/library/std/src/sys/personality/mod.rs b/library/std/src/sys/personality/mod.rs index 158e44e1764a3..eabef92244d01 100644 --- a/library/std/src/sys/personality/mod.rs +++ b/library/std/src/sys/personality/mod.rs @@ -17,7 +17,7 @@ cfg_select! { target_os = "emscripten" => { mod emcc; } - any(target_env = "msvc", target_family = "wasm") => { + any(target_env = "msvc", target_family = "wasm", target_os = "motor") => { // This is required by the compiler to exist (e.g., it's a lang item), // but it's never actually called by the compiler because // __CxxFrameHandler3 (msvc) / __gxx_wasm_personality_v0 (wasm) is the diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index a1ed0cd2cdd2d..92e459298fc4c 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -11,6 +11,10 @@ cfg_select! { mod uefi; use uefi as imp; } + target_os = "motor" => { + mod motor; + use motor as imp; + } _ => { mod unsupported; use unsupported as imp; @@ -38,6 +42,7 @@ pub use imp::{ )) ), target_os = "windows", + target_os = "motor" ))] pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec)> { use crate::sys::pipe::read2; @@ -77,5 +82,6 @@ pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec< )) ), target_os = "windows", + target_os = "motor" )))] pub use imp::output; diff --git a/library/std/src/sys/process/motor.rs b/library/std/src/sys/process/motor.rs new file mode 100644 index 0000000000000..9060902bc3d20 --- /dev/null +++ b/library/std/src/sys/process/motor.rs @@ -0,0 +1,313 @@ +use super::CommandEnvs; +use super::env::CommandEnv; +use crate::ffi::OsStr; +pub use crate::ffi::OsString as EnvKey; +use crate::num::NonZeroI32; +use crate::os::fd::{FromRawFd, IntoRawFd}; +use crate::os::motor::ffi::OsStrExt; +use crate::path::Path; +use crate::process::StdioPipes; +use crate::sys::fs::File; +use crate::sys::map_motor_error; +use crate::sys::pipe::AnonPipe; +use crate::sys_common::{AsInner, FromInner}; +use crate::{fmt, io}; + +pub enum Stdio { + Inherit, + Null, + MakePipe, + Fd(crate::sys::fd::FileDesc), +} + +impl Stdio { + fn into_rt(self) -> moto_rt::RtFd { + match self { + Stdio::Inherit => moto_rt::process::STDIO_INHERIT, + Stdio::Null => moto_rt::process::STDIO_NULL, + Stdio::MakePipe => moto_rt::process::STDIO_MAKE_PIPE, + Stdio::Fd(fd) => fd.into_raw_fd(), + } + } + + fn try_clone(&self) -> io::Result { + match self { + Self::Fd(fd) => { + Ok(Self::Fd(crate::sys::fd::FileDesc::from_inner(fd.as_inner().try_clone()?))) + } + Self::Inherit => Ok(Self::Inherit), + Self::Null => Ok(Self::Null), + Self::MakePipe => Ok(Self::MakePipe), + } + } +} + +#[derive(Default)] +pub struct Command { + program: String, + args: Vec, + cwd: Option, + stdin: Option, + stdout: Option, + stderr: Option, + env: CommandEnv, +} + +impl Command { + pub fn new(program: &OsStr) -> Command { + let mut env = CommandEnv::default(); + env.remove(OsStr::new(moto_rt::process::STDIO_IS_TERMINAL_ENV_KEY)); + + Command { program: program.as_str().to_owned(), env, ..Default::default() } + } + + pub fn arg(&mut self, arg: &OsStr) { + self.args.push(arg.as_str().to_owned()) + } + + pub fn env_mut(&mut self) -> &mut CommandEnv { + &mut self.env + } + + pub fn cwd(&mut self, dir: &OsStr) { + self.cwd = Some(dir.as_str().to_owned()) + } + + pub fn stdin(&mut self, stdin: Stdio) { + self.stdin = Some(stdin); + } + + pub fn stdout(&mut self, stdout: Stdio) { + self.stdout = Some(stdout); + } + + pub fn stderr(&mut self, stderr: Stdio) { + self.stderr = Some(stderr); + } + + pub fn get_program(&self) -> &OsStr { + OsStr::new(self.program.as_str()) + } + + pub fn get_args(&self) -> CommandArgs<'_> { + let iter = self.args.iter(); + CommandArgs { iter } + } + + pub fn get_envs(&self) -> CommandEnvs<'_> { + self.env.iter() + } + + pub fn get_current_dir(&self) -> Option<&Path> { + self.cwd.as_ref().map(Path::new) + } + + pub fn spawn( + &mut self, + default: Stdio, + needs_stdin: bool, + ) -> io::Result<(Process, StdioPipes)> { + let stdin = if let Some(stdin) = self.stdin.as_ref() { + stdin.try_clone()?.into_rt() + } else if needs_stdin { + default.try_clone()?.into_rt() + } else { + Stdio::Null.into_rt() + }; + let stdout = if let Some(stdout) = self.stdout.as_ref() { + stdout.try_clone()?.into_rt() + } else { + default.try_clone()?.into_rt() + }; + let stderr = if let Some(stderr) = self.stdout.as_ref() { + stderr.try_clone()?.into_rt() + } else { + default.try_clone()?.into_rt() + }; + + let mut env = Vec::<(String, String)>::new(); + for (k, v) in self.env.capture() { + env.push((k.as_str().to_owned(), v.as_str().to_owned())); + } + + let args = moto_rt::process::SpawnArgs { + program: self.program.clone(), + args: self.args.clone(), + env, + cwd: self.cwd.clone(), + stdin, + stdout, + stderr, + }; + + let (handle, stdin, stdout, stderr) = + moto_rt::process::spawn(args).map_err(map_motor_error)?; + + Ok(( + Process { handle }, + StdioPipes { + stdin: if stdin >= 0 { Some(stdin.into()) } else { None }, + stdout: if stdout >= 0 { Some(stdout.into()) } else { None }, + stderr: if stderr >= 0 { Some(stderr.into()) } else { None }, + }, + )) + } +} + +impl From for Stdio { + fn from(pipe: AnonPipe) -> Stdio { + unsafe { Stdio::Fd(crate::sys::fd::FileDesc::from_raw_fd(pipe.into_raw_fd())) } + } +} + +impl From for Stdio { + fn from(fd: crate::sys::fd::FileDesc) -> Stdio { + Stdio::Fd(fd) + } +} + +impl From for Stdio { + fn from(_file: File) -> Stdio { + panic!("Not implemented") + } +} + +impl From for Stdio { + fn from(_: io::Stdout) -> Stdio { + panic!("Not implemented") + } +} + +impl From for Stdio { + fn from(_: io::Stderr) -> Stdio { + panic!("Not implemented") + } +} + +impl fmt::Debug for Command { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + Ok(()) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub struct ExitStatus(i32); + +impl ExitStatus { + pub fn exit_ok(&self) -> Result<(), ExitStatusError> { + if self.0 == 0 { Ok(()) } else { 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, "exit code: {}", self.0) + } +} +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitStatusError(ExitStatus); + +impl Into for ExitStatusError { + fn into(self) -> ExitStatus { + self.0 + } +} + +impl ExitStatusError { + pub fn code(self) -> Option { + NonZeroI32::new(self.0.0) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitCode(i32); + +impl ExitCode { + pub const SUCCESS: ExitCode = ExitCode(0); + pub const FAILURE: ExitCode = ExitCode(1); + + pub fn as_i32(&self) -> i32 { + self.0 + } +} + +impl From for ExitCode { + fn from(code: u8) -> Self { + Self(code as i32) + } +} + +pub struct Process { + handle: u64, +} + +impl Drop for Process { + fn drop(&mut self) { + moto_rt::alloc::release_handle(self.handle).unwrap(); + } +} + +impl Process { + pub fn id(&self) -> u32 { + 0 + } + + pub fn kill(&mut self) -> io::Result<()> { + match moto_rt::process::kill(self.handle) { + moto_rt::E_OK => Ok(()), + err => Err(map_motor_error(err)), + } + } + + pub fn wait(&mut self) -> io::Result { + moto_rt::process::wait(self.handle).map(|c| ExitStatus(c)).map_err(map_motor_error) + } + + pub fn try_wait(&mut self) -> io::Result> { + match moto_rt::process::try_wait(self.handle) { + Ok(s) => Ok(Some(ExitStatus(s))), + Err(err) => match err { + moto_rt::E_NOT_READY => Ok(None), + err => Err(map_motor_error(err)), + }, + } + } + + #[allow(unused)] + pub fn handle(&self) -> u64 { + self.handle + } +} + +pub struct CommandArgs<'a> { + iter: crate::slice::Iter<'a, String>, +} + +impl<'a> Iterator for CommandArgs<'a> { + type Item = &'a OsStr; + fn next(&mut self) -> Option<&'a OsStr> { + self.iter.next().map(|arg| OsStr::new(arg)) + } + 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/random/mod.rs b/library/std/src/sys/random/mod.rs index ec81d89a0f3af..91f72d0738790 100644 --- a/library/std/src/sys/random/mod.rs +++ b/library/std/src/sys/random/mod.rs @@ -62,6 +62,10 @@ cfg_select! { mod redox; pub use redox::fill_bytes; } + target_os = "motor" => { + mod motor; + pub use motor::fill_bytes; + } all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; pub use sgx::fill_bytes; diff --git a/library/std/src/sys/random/motor.rs b/library/std/src/sys/random/motor.rs new file mode 100644 index 0000000000000..386b3704a91ea --- /dev/null +++ b/library/std/src/sys/random/motor.rs @@ -0,0 +1,3 @@ +pub fn fill_bytes(bytes: &mut [u8]) { + moto_rt::fill_random_bytes(bytes) +} diff --git a/library/std/src/sys/stdio/mod.rs b/library/std/src/sys/stdio/mod.rs index 660317e3ea844..d51ea9ad726b5 100644 --- a/library/std/src/sys/stdio/mod.rs +++ b/library/std/src/sys/stdio/mod.rs @@ -13,6 +13,10 @@ cfg_select! { mod sgx; pub use sgx::*; } + target_os = "motor" => { + mod motor; + pub use motor::*; + } target_os = "solid_asp3" => { mod solid; pub use solid::*; diff --git a/library/std/src/sys/stdio/motor.rs b/library/std/src/sys/stdio/motor.rs new file mode 100644 index 0000000000000..0a44feab723d0 --- /dev/null +++ b/library/std/src/sys/stdio/motor.rs @@ -0,0 +1,232 @@ +use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::sys::map_motor_error; +use crate::sys_common::{AsInner, FromInner, IntoInner}; +use crate::{io, process, sys}; + +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; + +pub struct Stdin {} + +impl Stdin { + pub const fn new() -> Self { + Self {} + } +} + +pub struct Stdout {} + +impl Stdout { + pub const fn new() -> Self { + Self {} + } +} + +pub struct Stderr {} + +impl Stderr { + pub const fn new() -> Self { + Self {} + } +} + +impl crate::sealed::Sealed for Stdin {} + +impl crate::io::IsTerminal for Stdin { + fn is_terminal(&self) -> bool { + moto_rt::fs::is_terminal(moto_rt::FD_STDIN) + } +} + +impl io::Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + moto_rt::fs::read(moto_rt::FD_STDIN, buf).map_err(map_motor_error) + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + moto_rt::fs::write(moto_rt::FD_STDOUT, buf).map_err(map_motor_error) + } + + fn flush(&mut self) -> io::Result<()> { + moto_rt::fs::flush(moto_rt::FD_STDOUT).map_err(map_motor_error) + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + moto_rt::fs::write(moto_rt::FD_STDERR, buf).map_err(map_motor_error) + } + + fn flush(&mut self) -> io::Result<()> { + moto_rt::fs::flush(moto_rt::FD_STDERR).map_err(map_motor_error) + } +} + +pub fn panic_output() -> Option { + Some(Stderr::new()) +} + +pub fn is_ebadf(_err: &io::Error) -> bool { + true +} + +#[stable(feature = "process_extensions", since = "1.2.0")] +impl FromRawFd for process::Stdio { + #[inline] + unsafe fn from_raw_fd(fd: RawFd) -> process::Stdio { + let fd = unsafe { sys::fd::FileDesc::from_raw_fd(fd) }; + let io = sys::process::Stdio::Fd(fd); + process::Stdio::from_inner(io) + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for process::Stdio { + /// Takes ownership of a file descriptor and returns a [`Stdio`](process::Stdio) + /// that can attach a stream to it. + #[inline] + fn from(fd: OwnedFd) -> process::Stdio { + let fd = sys::fd::FileDesc::from_inner(fd); + let io = sys::process::Stdio::Fd(fd); + process::Stdio::from_inner(io) + } +} + +#[stable(feature = "process_extensions", since = "1.2.0")] +impl AsRawFd for process::ChildStdin { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.as_inner().as_raw_fd() + } +} + +#[stable(feature = "process_extensions", since = "1.2.0")] +impl AsRawFd for process::ChildStdout { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.as_inner().as_raw_fd() + } +} + +#[stable(feature = "process_extensions", since = "1.2.0")] +impl AsRawFd for process::ChildStderr { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.as_inner().as_raw_fd() + } +} + +#[stable(feature = "into_raw_os", since = "1.4.0")] +impl IntoRawFd for process::ChildStdin { + #[inline] + fn into_raw_fd(self) -> RawFd { + self.into_inner().into_raw_fd() + } +} + +#[stable(feature = "into_raw_os", since = "1.4.0")] +impl IntoRawFd for process::ChildStdout { + #[inline] + fn into_raw_fd(self) -> RawFd { + self.into_inner().into_raw_fd() + } +} + +#[stable(feature = "into_raw_os", since = "1.4.0")] +impl IntoRawFd for process::ChildStderr { + #[inline] + fn into_raw_fd(self) -> RawFd { + self.into_inner().into_raw_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for crate::process::ChildStdin { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.as_inner().as_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + /// Takes ownership of a [`ChildStdin`](crate::process::ChildStdin)'s file descriptor. + #[inline] + fn from(child_stdin: crate::process::ChildStdin) -> OwnedFd { + child_stdin.into_inner().into_inner() + } +} + +/// Creates a `ChildStdin` from the provided `OwnedFd`. +/// +/// The provided file descriptor must point to a pipe +/// with the `CLOEXEC` flag set. +#[stable(feature = "child_stream_from_fd", since = "1.74.0")] +impl From for process::ChildStdin { + #[inline] + fn from(fd: OwnedFd) -> process::ChildStdin { + let pipe = sys::pipe::AnonPipe::from_inner(fd); + process::ChildStdin::from_inner(pipe) + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for crate::process::ChildStdout { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.as_inner().as_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + /// Takes ownership of a [`ChildStdout`](crate::process::ChildStdout)'s file descriptor. + #[inline] + fn from(child_stdout: crate::process::ChildStdout) -> OwnedFd { + child_stdout.into_inner().into_inner() + } +} + +/// Creates a `ChildStdout` from the provided `OwnedFd`. +/// +/// The provided file descriptor must point to a pipe +/// with the `CLOEXEC` flag set. +#[stable(feature = "child_stream_from_fd", since = "1.74.0")] +impl From for process::ChildStdout { + #[inline] + fn from(fd: OwnedFd) -> process::ChildStdout { + let pipe = sys::pipe::AnonPipe::from_inner(fd); + process::ChildStdout::from_inner(pipe) + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for crate::process::ChildStderr { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.as_inner().as_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + /// Takes ownership of a [`ChildStderr`](crate::process::ChildStderr)'s file descriptor. + #[inline] + fn from(child_stderr: crate::process::ChildStderr) -> OwnedFd { + child_stderr.into_inner().into_inner() + } +} + +/// Creates a `ChildStderr` from the provided `OwnedFd`. +/// +/// The provided file descriptor must point to a pipe +/// with the `CLOEXEC` flag set. +#[stable(feature = "child_stream_from_fd", since = "1.74.0")] +impl From for process::ChildStderr { + #[inline] + fn from(fd: OwnedFd) -> process::ChildStderr { + let pipe = sys::pipe::AnonPipe::from_inner(fd); + process::ChildStderr::from_inner(pipe) + } +} diff --git a/library/std/src/sys/sync/condvar/mod.rs b/library/std/src/sys/sync/condvar/mod.rs index cb67d273759dd..83cf0ae629851 100644 --- a/library/std/src/sys/sync/condvar/mod.rs +++ b/library/std/src/sys/sync/condvar/mod.rs @@ -6,6 +6,7 @@ cfg_select! { target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", + target_os = "motor", target_os = "fuchsia", 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 c885b0eabae2d..e3d6ad1129c83 100644 --- a/library/std/src/sys/sync/mutex/mod.rs +++ b/library/std/src/sys/sync/mutex/mod.rs @@ -5,6 +5,7 @@ cfg_select! { target_os = "android", target_os = "freebsd", target_os = "openbsd", + target_os = "motor", target_os = "dragonfly", 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 8adeb1f259d72..aeea884b9f617 100644 --- a/library/std/src/sys/sync/once/mod.rs +++ b/library/std/src/sys/sync/once/mod.rs @@ -14,6 +14,7 @@ cfg_select! { target_os = "android", all(target_arch = "wasm32", target_feature = "atomics"), target_os = "freebsd", + target_os = "motor", target_os = "openbsd", target_os = "dragonfly", target_os = "fuchsia", diff --git a/library/std/src/sys/sync/rwlock/mod.rs b/library/std/src/sys/sync/rwlock/mod.rs index 82f1dd18dee49..ab5715bf2de33 100644 --- a/library/std/src/sys/sync/rwlock/mod.rs +++ b/library/std/src/sys/sync/rwlock/mod.rs @@ -9,6 +9,7 @@ cfg_select! { target_os = "fuchsia", all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", + target_os = "motor", ) => { mod futex; pub use futex::RwLock; diff --git a/library/std/src/sys/sync/thread_parking/mod.rs b/library/std/src/sys/sync/thread_parking/mod.rs index b9fb27b4eef2d..e8a9dc884f816 100644 --- a/library/std/src/sys/sync/thread_parking/mod.rs +++ b/library/std/src/sys/sync/thread_parking/mod.rs @@ -8,6 +8,7 @@ cfg_select! { target_os = "openbsd", target_os = "dragonfly", target_os = "fuchsia", + target_os = "motor", target_os = "hermit", ) => { mod futex; diff --git a/library/std/src/sys/thread/mod.rs b/library/std/src/sys/thread/mod.rs index a20b2a3ddd8ce..b98be62be0ad2 100644 --- a/library/std/src/sys/thread/mod.rs +++ b/library/std/src/sys/thread/mod.rs @@ -6,6 +6,10 @@ cfg_select! { mod unsupported; pub use unsupported::{current_os_id, set_name}; } + target_os = "motor" => { + mod motor; + pub use motor::*; + } all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; pub use sgx::{Thread, current_os_id, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; diff --git a/library/std/src/sys/thread/motor.rs b/library/std/src/sys/thread/motor.rs new file mode 100644 index 0000000000000..0457d8818f326 --- /dev/null +++ b/library/std/src/sys/thread/motor.rs @@ -0,0 +1,63 @@ +use crate::ffi::CStr; +use crate::io; +use crate::num::NonZeroUsize; +use crate::sys::map_motor_error; +use crate::time::Duration; + +pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 256; + +pub struct Thread { + sys_thread: moto_rt::thread::ThreadHandle, +} + +unsafe impl Send for Thread {} +unsafe impl Sync for Thread {} + +impl Thread { + pub unsafe fn new( + stack: usize, + _name: Option<&str>, + p: Box, + ) -> io::Result { + extern "C" fn __moto_rt_thread_fn(thread_arg: u64) { + unsafe { + Box::from_raw( + core::ptr::with_exposed_provenance::>(thread_arg as usize) + .cast_mut(), + )(); + } + } + + let thread_arg = Box::into_raw(Box::new(p)).expose_provenance() as u64; + let sys_thread = moto_rt::thread::spawn(__moto_rt_thread_fn, stack, thread_arg) + .map_err(map_motor_error)?; + Ok(Self { sys_thread }) + } + + pub fn join(self) { + assert!(moto_rt::thread::join(self.sys_thread) == moto_rt::E_OK) + } +} + +pub fn set_name(name: &CStr) { + let bytes = name.to_bytes(); + if let Ok(s) = core::str::from_utf8(bytes) { + let _ = moto_rt::thread::set_name(s); + } +} + +pub fn current_os_id() -> Option { + None +} + +pub fn available_parallelism() -> io::Result { + Ok(unsafe { NonZeroUsize::new_unchecked(moto_rt::num_cpus()) }) +} + +pub fn yield_now() { + moto_rt::thread::yield_now() +} + +pub fn sleep(dur: Duration) { + moto_rt::thread::sleep_until(moto_rt::time::Instant::now() + dur) +} diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index f7f051b1addc4..e88011aa22dad 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -187,6 +187,14 @@ pub(crate) mod key { pub(super) use xous::{Key, get, set}; use xous::{create, destroy}; } + target_os = "motor" => { + mod racy; + #[cfg(test)] + mod tests; + pub(super) use racy::LazyKey; + pub(super) use moto_rt::tls::{Key, get, set}; + use moto_rt::tls::{create, destroy}; + } _ => {} } } From e4ead0ec7027ea819dd93a3f10fe3cd1157337c4 Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Fri, 25 Jul 2025 07:50:46 +0100 Subject: [PATCH 03/24] Guard HIR lowered contracts with contract_checks Refactor contract HIR lowering to ensure no contract code is executed when contract-checks are disabled. The call to contract_checks is moved to inside the lowered fn body, and contract closures are built conditionally, ensuring no side-effects present in contracts occur when those are disabled. --- compiler/rustc_ast_lowering/src/contract.rs | 260 ++++++++++++++++++ compiler/rustc_ast_lowering/src/expr.rs | 86 +++--- compiler/rustc_ast_lowering/src/item.rs | 71 +---- compiler/rustc_ast_lowering/src/lib.rs | 1 + compiler/rustc_hir/src/lang_items.rs | 1 + .../rustc_hir_analysis/src/check/intrinsic.rs | 4 +- compiler/rustc_middle/src/ty/sty.rs | 6 + library/core/src/intrinsics/mod.rs | 25 +- .../contracts-disabled-side-effect-ensures.rs | 17 ++ ...tracts-disabled-side-effect-ensures.stderr | 11 + tests/ui/contracts/empty-ensures.rs | 16 ++ tests/ui/contracts/empty-ensures.stderr | 25 ++ tests/ui/contracts/empty-requires.rs | 18 ++ tests/ui/contracts/empty-requires.stderr | 18 ++ .../internal_machinery/contract-intrinsics.rs | 12 +- .../internal_machinery/contract-lang-items.rs | 6 +- .../internal-feature-gating.rs | 2 +- .../internal-feature-gating.stderr | 2 +- 18 files changed, 461 insertions(+), 120 deletions(-) create mode 100644 compiler/rustc_ast_lowering/src/contract.rs create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-ensures.rs create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr create mode 100644 tests/ui/contracts/empty-ensures.rs create mode 100644 tests/ui/contracts/empty-ensures.stderr create mode 100644 tests/ui/contracts/empty-requires.rs create mode 100644 tests/ui/contracts/empty-requires.stderr diff --git a/compiler/rustc_ast_lowering/src/contract.rs b/compiler/rustc_ast_lowering/src/contract.rs new file mode 100644 index 0000000000000..ad49cd2a42423 --- /dev/null +++ b/compiler/rustc_ast_lowering/src/contract.rs @@ -0,0 +1,260 @@ +use crate::LoweringContext; + +impl<'a, 'hir> LoweringContext<'a, 'hir> { + pub(super) fn lower_contract( + &mut self, + body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>, + contract: &rustc_ast::FnContract, + ) -> rustc_hir::Expr<'hir> { + match (&contract.requires, &contract.ensures) { + (Some(req), Some(ens)) => { + // Lower the fn contract, which turns: + // + // { body } + // + // into: + // + // { + // let __postcond = if contracts_checks() { + // contract_check_requires(PRECOND); + // Some(|ret_val| POSTCOND) + // } else { + // None + // }; + // contract_check_ensures(__postcond, { body }) + // } + + let precond = self.lower_precond(req); + let postcond_checker = self.lower_postcond_checker(ens); + + let contract_check = + self.lower_contract_check_with_postcond(Some(precond), postcond_checker); + + let wrapped_body = + self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span); + self.expr_block(wrapped_body) + } + (None, Some(ens)) => { + // Lower the fn contract, which turns: + // + // { body } + // + // into: + // + // { + // let __postcond = if contracts_check() { + // Some(|ret_val| POSTCOND) + // } else { + // None + // }; + // __postcond({ body }) + // } + + let postcond_checker = self.lower_postcond_checker(ens); + let contract_check = + self.lower_contract_check_with_postcond(None, postcond_checker); + + let wrapped_body = + self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span); + self.expr_block(wrapped_body) + } + (Some(req), None) => { + // Lower the fn contract, which turns: + // + // { body } + // + // into: + // + // { + // if contracts_check() { + // contract_requires(PRECOND); + // } + // body + // } + let precond = self.lower_precond(req); + let precond_check = self.lower_contract_check_just_precond(precond); + + let body = self.arena.alloc(body(self)); + + // Flatten the body into precond check, then body. + let wrapped_body = self.block_all( + body.span, + self.arena.alloc_from_iter([precond_check].into_iter()), + Some(body), + ); + self.expr_block(wrapped_body) + } + (None, None) => body(self), + } + } + + /// Lower the precondition check intrinsic. + fn lower_precond(&mut self, req: &Box) -> rustc_hir::Stmt<'hir> { + let lowered_req = self.lower_expr_mut(&req); + let req_span = self.mark_span_with_reason( + rustc_span::DesugaringKind::Contract, + lowered_req.span, + None, + ); + let precond = self.expr_call_lang_item_fn_mut( + req_span, + rustc_hir::LangItem::ContractCheckRequires, + &*arena_vec![self; lowered_req], + ); + self.stmt_expr(req.span, precond) + } + + fn lower_postcond_checker( + &mut self, + ens: &Box, + ) -> &'hir rustc_hir::Expr<'hir> { + let ens_span = self.lower_span(ens.span); + let ens_span = + self.mark_span_with_reason(rustc_span::DesugaringKind::Contract, ens_span, None); + let lowered_ens = self.lower_expr_mut(&ens); + self.expr_call_lang_item_fn( + ens_span, + rustc_hir::LangItem::ContractBuildCheckEnsures, + &*arena_vec![self; lowered_ens], + ) + } + + fn lower_contract_check_just_precond( + &mut self, + precond: rustc_hir::Stmt<'hir>, + ) -> rustc_hir::Stmt<'hir> { + let stmts = self.arena.alloc_from_iter([precond].into_iter()); + + let then_block_stmts = self.block_all(precond.span, stmts, None); + let then_block = self.arena.alloc(self.expr_block(&then_block_stmts)); + + let precond_check = rustc_hir::ExprKind::If( + self.expr_call_lang_item_fn( + precond.span, + rustc_hir::LangItem::ContractChecks, + Default::default(), + ), + then_block, + None, + ); + + let precond_check = self.expr(precond.span, precond_check); + self.stmt_expr(precond.span, precond_check) + } + + fn lower_contract_check_with_postcond( + &mut self, + precond: Option>, + postcond_checker: &'hir rustc_hir::Expr<'hir>, + ) -> &'hir rustc_hir::Expr<'hir> { + let stmts = self.arena.alloc_from_iter(precond.into_iter()); + let span = match precond { + Some(precond) => precond.span, + None => postcond_checker.span, + }; + + let postcond_checker = self.arena.alloc(self.expr_enum_variant_lang_item( + postcond_checker.span, + rustc_hir::lang_items::LangItem::OptionSome, + &*arena_vec![self; *postcond_checker], + )); + let then_block_stmts = self.block_all(span, stmts, Some(postcond_checker)); + let then_block = self.arena.alloc(self.expr_block(&then_block_stmts)); + + let none_expr = self.arena.alloc(self.expr_enum_variant_lang_item( + postcond_checker.span, + rustc_hir::lang_items::LangItem::OptionNone, + Default::default(), + )); + let else_block = self.block_expr(none_expr); + let else_block = self.arena.alloc(self.expr_block(else_block)); + + let contract_check = rustc_hir::ExprKind::If( + self.expr_call_lang_item_fn( + span, + rustc_hir::LangItem::ContractChecks, + Default::default(), + ), + then_block, + Some(else_block), + ); + self.arena.alloc(self.expr(span, contract_check)) + } + + fn wrap_body_with_contract_check( + &mut self, + body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>, + contract_check: &'hir rustc_hir::Expr<'hir>, + postcond_span: rustc_span::Span, + ) -> &'hir rustc_hir::Block<'hir> { + let check_ident: rustc_span::Ident = + rustc_span::Ident::from_str_and_span("__ensures_checker", postcond_span); + let (check_hir_id, postcond_decl) = { + // Set up the postcondition `let` statement. + let (checker_pat, check_hir_id) = self.pat_ident_binding_mode_mut( + postcond_span, + check_ident, + rustc_hir::BindingMode::NONE, + ); + ( + check_hir_id, + self.stmt_let_pat( + None, + postcond_span, + Some(contract_check), + self.arena.alloc(checker_pat), + rustc_hir::LocalSource::Contract, + ), + ) + }; + + // Install contract_ensures so we will intercept `return` statements, + // then lower the body. + self.contract_ensures = Some((postcond_span, check_ident, check_hir_id)); + let body = self.arena.alloc(body(self)); + + // Finally, inject an ensures check on the implicit return of the body. + let body = self.inject_ensures_check(body, postcond_span, check_ident, check_hir_id); + + // Flatten the body into precond, then postcond, then wrapped body. + let wrapped_body = self.block_all( + body.span, + self.arena.alloc_from_iter([postcond_decl].into_iter()), + Some(body), + ); + wrapped_body + } + + /// Create an `ExprKind::Ret` that is optionally wrapped by a call to check + /// a contract ensures clause, if it exists. + pub(super) fn checked_return( + &mut self, + opt_expr: Option<&'hir rustc_hir::Expr<'hir>>, + ) -> rustc_hir::ExprKind<'hir> { + let checked_ret = + if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures { + let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span)); + Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id)) + } else { + opt_expr + }; + rustc_hir::ExprKind::Ret(checked_ret) + } + + /// Wraps an expression with a call to the ensures check before it gets returned. + pub(super) fn inject_ensures_check( + &mut self, + expr: &'hir rustc_hir::Expr<'hir>, + span: rustc_span::Span, + cond_ident: rustc_span::Ident, + cond_hir_id: rustc_hir::HirId, + ) -> &'hir rustc_hir::Expr<'hir> { + let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id); + let call_expr = self.expr_call_lang_item_fn_mut( + span, + rustc_hir::LangItem::ContractCheckEnsures, + arena_vec![self; *cond_fn, *expr], + ); + self.arena.alloc(call_expr) + } +} diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index bb6b25baf013d..d1ce3b169d75f 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -383,36 +383,6 @@ impl<'hir> LoweringContext<'_, 'hir> { }) } - /// Create an `ExprKind::Ret` that is optionally wrapped by a call to check - /// a contract ensures clause, if it exists. - fn checked_return(&mut self, opt_expr: Option<&'hir hir::Expr<'hir>>) -> hir::ExprKind<'hir> { - let checked_ret = - if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures { - let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span)); - Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id)) - } else { - opt_expr - }; - hir::ExprKind::Ret(checked_ret) - } - - /// Wraps an expression with a call to the ensures check before it gets returned. - pub(crate) fn inject_ensures_check( - &mut self, - expr: &'hir hir::Expr<'hir>, - span: Span, - cond_ident: Ident, - cond_hir_id: HirId, - ) -> &'hir hir::Expr<'hir> { - let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id); - let call_expr = self.expr_call_lang_item_fn_mut( - span, - hir::LangItem::ContractCheckEnsures, - arena_vec![self; *cond_fn, *expr], - ); - self.arena.alloc(call_expr) - } - pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock { self.with_new_scopes(c.value.span, |this| { let def_id = this.local_def_id(c.id); @@ -2120,7 +2090,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e)) } - fn expr_unit(&mut self, sp: Span) -> &'hir hir::Expr<'hir> { + pub(super) fn expr_unit(&mut self, sp: Span) -> &'hir hir::Expr<'hir> { self.arena.alloc(self.expr(sp, hir::ExprKind::Tup(&[]))) } @@ -2161,6 +2131,43 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::Call(e, args)) } + pub(super) fn expr_struct( + &mut self, + span: Span, + path: &'hir hir::QPath<'hir>, + fields: &'hir [hir::ExprField<'hir>], + ) -> hir::Expr<'hir> { + self.expr(span, hir::ExprKind::Struct(path, fields, rustc_hir::StructTailExpr::None)) + } + + pub(super) fn expr_enum_variant( + &mut self, + span: Span, + path: &'hir hir::QPath<'hir>, + fields: &'hir [hir::Expr<'hir>], + ) -> hir::Expr<'hir> { + let fields = self.arena.alloc_from_iter(fields.into_iter().enumerate().map(|(i, f)| { + hir::ExprField { + hir_id: self.next_id(), + ident: Ident::from_str(&i.to_string()), + expr: f, + span: f.span, + is_shorthand: false, + } + })); + self.expr_struct(span, path, fields) + } + + pub(super) fn expr_enum_variant_lang_item( + &mut self, + span: Span, + lang_item: hir::LangItem, + fields: &'hir [hir::Expr<'hir>], + ) -> hir::Expr<'hir> { + let path = self.arena.alloc(self.lang_item_path(span, lang_item)); + self.expr_enum_variant(span, path, fields) + } + pub(super) fn expr_call( &mut self, span: Span, @@ -2189,8 +2196,21 @@ impl<'hir> LoweringContext<'_, 'hir> { self.arena.alloc(self.expr_call_lang_item_fn_mut(span, lang_item, args)) } - fn expr_lang_item_path(&mut self, span: Span, lang_item: hir::LangItem) -> hir::Expr<'hir> { - self.expr(span, hir::ExprKind::Path(hir::QPath::LangItem(lang_item, self.lower_span(span)))) + pub(super) fn expr_lang_item_path( + &mut self, + span: Span, + lang_item: hir::LangItem, + ) -> hir::Expr<'hir> { + let path = self.lang_item_path(span, lang_item); + self.expr(span, hir::ExprKind::Path(path)) + } + + pub(super) fn lang_item_path( + &mut self, + span: Span, + lang_item: hir::LangItem, + ) -> hir::QPath<'hir> { + hir::QPath::LangItem(lang_item, self.lower_span(span)) } /// `::name` diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 53351f91c46bc..d11b7f798b6db 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1214,76 +1214,9 @@ impl<'hir> LoweringContext<'_, 'hir> { let params = this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))); - // Optionally lower the fn contract, which turns: - // - // { body } - // - // into: - // - // { contract_requires(PRECOND); let __postcond = |ret_val| POSTCOND; postcond({ body }) } + // Optionally lower the fn contract if let Some(contract) = contract { - let precond = if let Some(req) = &contract.requires { - // Lower the precondition check intrinsic. - let lowered_req = this.lower_expr_mut(&req); - let req_span = this.mark_span_with_reason( - DesugaringKind::Contract, - lowered_req.span, - None, - ); - let precond = this.expr_call_lang_item_fn_mut( - req_span, - hir::LangItem::ContractCheckRequires, - &*arena_vec![this; lowered_req], - ); - Some(this.stmt_expr(req.span, precond)) - } else { - None - }; - let (postcond, body) = if let Some(ens) = &contract.ensures { - let ens_span = this.lower_span(ens.span); - let ens_span = - this.mark_span_with_reason(DesugaringKind::Contract, ens_span, None); - // Set up the postcondition `let` statement. - let check_ident: Ident = - Ident::from_str_and_span("__ensures_checker", ens_span); - let (checker_pat, check_hir_id) = this.pat_ident_binding_mode_mut( - ens_span, - check_ident, - hir::BindingMode::NONE, - ); - let lowered_ens = this.lower_expr_mut(&ens); - let postcond_checker = this.expr_call_lang_item_fn( - ens_span, - hir::LangItem::ContractBuildCheckEnsures, - &*arena_vec![this; lowered_ens], - ); - let postcond = this.stmt_let_pat( - None, - ens_span, - Some(postcond_checker), - this.arena.alloc(checker_pat), - hir::LocalSource::Contract, - ); - - // Install contract_ensures so we will intercept `return` statements, - // then lower the body. - this.contract_ensures = Some((ens_span, check_ident, check_hir_id)); - let body = this.arena.alloc(body(this)); - - // Finally, inject an ensures check on the implicit return of the body. - let body = this.inject_ensures_check(body, ens_span, check_ident, check_hir_id); - (Some(postcond), body) - } else { - let body = &*this.arena.alloc(body(this)); - (None, body) - }; - // Flatten the body into precond, then postcond, then wrapped body. - let wrapped_body = this.block_all( - body.span, - this.arena.alloc_from_iter([precond, postcond].into_iter().flatten()), - Some(body), - ); - (params, this.expr_block(wrapped_body)) + (params, this.lower_contract(body, contract)) } else { (params, body(this)) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 4e2243e87873c..282a62ca44310 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -77,6 +77,7 @@ macro_rules! arena_vec { mod asm; mod block; +mod contract; mod delegation; mod errors; mod expr; diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 2e099a97b65be..d62ec5482b430 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -429,6 +429,7 @@ language_item_table! { // Experimental lang items for implementing contract pre- and post-condition checking. ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None; ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None; + ContractChecks, sym::contract_checks, contract_checks_fn, Target::Fn, GenericRequirement::None; // Experimental lang items for `MCP: Low level components for async drop`(https://github.com/rust-lang/compiler-team/issues/727) DefaultTrait4, sym::default_trait4, default_trait4_trait, Target::Trait, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index bc3448be5823e..5fd71fd1cd76c 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -651,7 +651,9 @@ pub(crate) fn check_intrinsic_type( sym::contract_checks => (0, 0, Vec::new(), tcx.types.bool), // contract_check_requires::(C) -> bool, where C: impl Fn() -> bool sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit), - sym::contract_check_ensures => (2, 0, vec![param(0), param(1)], param(1)), + sym::contract_check_ensures => { + (2, 0, vec![Ty::new_option(tcx, param(0)), param(1)], param(1)) + } sym::simd_eq | sym::simd_ne | sym::simd_lt | sym::simd_le | sym::simd_gt | sym::simd_ge => { (2, 0, vec![param(0), param(0)], param(1)) diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index de35e5e847c81..7f44217971833 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -903,6 +903,12 @@ impl<'tcx> Ty<'tcx> { Ty::new_generic_adt(tcx, def_id, ty) } + #[inline] + pub fn new_option(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + let def_id = tcx.require_lang_item(LangItem::Option, DUMMY_SP); + Ty::new_generic_adt(tcx, def_id, ty) + } + #[inline] pub fn new_maybe_uninit(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { let def_id = tcx.require_lang_item(LangItem::MaybeUninit, DUMMY_SP); diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index cef700be9ea1f..0d3be136d1325 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2637,9 +2637,10 @@ pub const unsafe fn const_make_global(ptr: *mut u8) -> *const u8 { /// of not prematurely committing at compile-time to whether contract /// checking is turned on, so that we can specify contracts in libstd /// and let an end user opt into turning them on. -#[rustc_const_unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] #[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] +#[rustc_const_unstable(feature = "contracts", issue = "128044")] #[inline(always)] +#[lang = "contract_checks"] #[rustc_intrinsic] pub const fn contract_checks() -> bool { // FIXME: should this be `false` or `cfg!(contract_checks)`? @@ -2668,7 +2669,7 @@ pub const fn contract_check_requires bool + Copy>(cond: C) { if const { // Do nothing } else { - if contract_checks() && !cond() { + if !cond() { // Emit no unwind panic in case this was a safety requirement. crate::panicking::panic_nounwind("failed requires check"); } @@ -2681,6 +2682,8 @@ pub const fn contract_check_requires bool + Copy>(cond: C) { /// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition /// returns false. /// +/// If `cond` is `None`, then no postcondition checking is performed. +/// /// Note that this function is a no-op during constant evaluation. #[unstable(feature = "contracts_internals", issue = "128044")] // Similar to `contract_check_requires`, we need to use the user-facing @@ -2689,16 +2692,24 @@ pub const fn contract_check_requires bool + Copy>(cond: C) { #[rustc_const_unstable(feature = "contracts", issue = "128044")] #[lang = "contract_check_ensures"] #[rustc_intrinsic] -pub const fn contract_check_ensures bool + Copy, Ret>(cond: C, ret: Ret) -> Ret { +pub const fn contract_check_ensures bool + Copy, Ret>( + cond: Option, + ret: Ret, +) -> Ret { const_eval_select!( - @capture[C: Fn(&Ret) -> bool + Copy, Ret] { cond: C, ret: Ret } -> Ret : + @capture[C: Fn(&Ret) -> bool + Copy, Ret] { cond: Option, ret: Ret } -> Ret : if const { // Do nothing ret } else { - if contract_checks() && !cond(&ret) { - // Emit no unwind panic in case this was a safety requirement. - crate::panicking::panic_nounwind("failed ensures check"); + match cond { + crate::option::Option::Some(cond) => { + if !cond(&ret) { + // Emit no unwind panic in case this was a safety requirement. + crate::panicking::panic_nounwind("failed ensures check"); + } + }, + crate::option::Option::None => {}, } ret } diff --git a/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs b/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs new file mode 100644 index 0000000000000..d234acb8268a1 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs @@ -0,0 +1,17 @@ +//@ run-pass +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +extern crate core; +use core::contracts::ensures; + +#[ensures({*x = 0; |_ret| true})] +fn buggy_add(x: &mut u32, y: u32) { + *x = *x + y; +} + +fn main() { + let mut x = 10; + buggy_add(&mut x, 100); + assert_eq!(x, 110); +} diff --git a/tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr b/tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr new file mode 100644 index 0000000000000..dd9ebe9bd3556 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-disabled-side-effect-ensures.rs:2:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/empty-ensures.rs b/tests/ui/contracts/empty-ensures.rs new file mode 100644 index 0000000000000..d897f27bf6c94 --- /dev/null +++ b/tests/ui/contracts/empty-ensures.rs @@ -0,0 +1,16 @@ +//@ compile-flags: -Zcontract-checks=yes +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +extern crate core; +use core::contracts::ensures; + +#[ensures()] +//~^ ERROR expected a `Fn(&_)` closure, found `()` [E0277] +fn foo(x: u32) -> u32 { + x * 2 +} + +fn main() { + foo(1); +} diff --git a/tests/ui/contracts/empty-ensures.stderr b/tests/ui/contracts/empty-ensures.stderr new file mode 100644 index 0000000000000..407a253bd8565 --- /dev/null +++ b/tests/ui/contracts/empty-ensures.stderr @@ -0,0 +1,25 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/empty-ensures.rs:2:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +error[E0277]: expected a `Fn(&_)` closure, found `()` + --> $DIR/empty-ensures.rs:8:1 + | +LL | #[ensures()] + | ^^^^^^^^^^^^ + | | + | expected an `Fn(&_)` closure, found `()` + | required by a bound introduced by this call + | + = help: the trait `for<'a> Fn(&'a _)` is not implemented for `()` +note: required by a bound in `build_check_ensures` + --> $SRC_DIR/core/src/contracts.rs:LL:COL + +error: aborting due to 1 previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/empty-requires.rs b/tests/ui/contracts/empty-requires.rs new file mode 100644 index 0000000000000..e3c72dcd66a17 --- /dev/null +++ b/tests/ui/contracts/empty-requires.rs @@ -0,0 +1,18 @@ +//@ dont-require-annotations: NOTE +//@ compile-flags: -Zcontract-checks=yes +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +extern crate core; +use core::contracts::requires; + +#[requires()] +//~^ ERROR mismatched types [E0308] +//~| NOTE expected `bool`, found `()` +fn foo(x: u32) -> u32 { + x * 2 +} + +fn main() { + foo(1); +} diff --git a/tests/ui/contracts/empty-requires.stderr b/tests/ui/contracts/empty-requires.stderr new file mode 100644 index 0000000000000..b48e547b8cda7 --- /dev/null +++ b/tests/ui/contracts/empty-requires.stderr @@ -0,0 +1,18 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/empty-requires.rs:3:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +error[E0308]: mismatched types + --> $DIR/empty-requires.rs:9:1 + | +LL | #[requires()] + | ^^^^^^^^^^^^^ expected `bool`, found `()` + +error: aborting due to 1 previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs index 6e613b53fc9b1..5caea2b23d93c 100644 --- a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -22,15 +22,15 @@ fn main() { // always pass core::intrinsics::contract_check_requires(|| true); - // fail if enabled - #[cfg(any(default, unchk_pass, chk_fail_requires))] + // always fail + #[cfg(any(chk_fail_requires))] core::intrinsics::contract_check_requires(|| false); let doubles_to_two = { let old = 2; move |ret: &u32 | ret + ret == old }; // Always pass - core::intrinsics::contract_check_ensures(doubles_to_two, 1); + core::intrinsics::contract_check_ensures(Some(doubles_to_two), 1); - // Fail if enabled - #[cfg(any(default, unchk_pass, chk_fail_ensures))] - core::intrinsics::contract_check_ensures(doubles_to_two, 2); + // always fail + #[cfg(any(chk_fail_ensures))] + core::intrinsics::contract_check_ensures(Some(doubles_to_two), 2); } diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs index ac72d233bf6b4..50cf592ac564d 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.rs +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -17,8 +17,10 @@ #![feature(contracts_internals)] // to access check_requires lang item #![feature(core_intrinsics)] fn foo(x: Baz) -> i32 { - let injected_checker = { - core::contracts::build_check_ensures(|ret| *ret > 100) + let injected_checker = if core::intrinsics::contract_checks() { + Some(core::contracts::build_check_ensures(|ret| *ret > 100)) + } else { + None }; let ret = x.baz + 50; diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs index 6e5a7a3f95005..d101ab33547eb 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -6,7 +6,7 @@ fn main() { //~^ ERROR use of unstable library feature `contracts_internals` core::intrinsics::contract_check_requires(|| true); //~^ ERROR use of unstable library feature `contracts_internals` - core::intrinsics::contract_check_ensures( |_|true, &1); + core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); //~^ ERROR use of unstable library feature `contracts_internals` core::contracts::build_check_ensures(|_: &()| true); diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr index 1e39bd62e245b..738b7a3e09ff1 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -41,7 +41,7 @@ LL | core::intrinsics::contract_check_requires(|| true); error[E0658]: use of unstable library feature `contracts_internals` --> $DIR/internal-feature-gating.rs:9:5 | -LL | core::intrinsics::contract_check_ensures( |_|true, &1); +LL | core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #128044 for more information From 2d87527e426509b184fb1567c762420e0d513add Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Sat, 16 Aug 2025 15:37:53 +0100 Subject: [PATCH 04/24] Guard HIR contracts based on compiler flag rather than lang_item This allows the optimiser to properly eliminate contract code when runtime contract checks are disabled. It comes at the cost of having to recompile upstream crates (e.g. std) to enable contracts in them. However, this trade off is acceptable if it means disabled runtime contract checks do not affect the runtime performance of the functions they annotate. With the proper elimination of contract code, which this change introduces, the runtime performance of annotated functions should be the same as the original unannotated function. --- compiler/rustc_ast_lowering/src/contract.rs | 118 ++++++++++++++---- compiler/rustc_ast_lowering/src/expr.rs | 39 ++++-- ...contract-lang-items.unchk_fail_post.stderr | 11 -- 3 files changed, 120 insertions(+), 48 deletions(-) delete mode 100644 tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr diff --git a/compiler/rustc_ast_lowering/src/contract.rs b/compiler/rustc_ast_lowering/src/contract.rs index ad49cd2a42423..2f1c3d66d4e70 100644 --- a/compiler/rustc_ast_lowering/src/contract.rs +++ b/compiler/rustc_ast_lowering/src/contract.rs @@ -1,6 +1,18 @@ +use thin_vec::thin_vec; + use crate::LoweringContext; impl<'a, 'hir> LoweringContext<'a, 'hir> { + /// Lowered contracts are guarded with the `contract_checks` compiler flag, + /// i.e. the flag turns into a boolean guard in the lowered HIR. The reason + /// for not eliminating the contract code entirely when the `contract_checks` + /// flag is disabled is so that contracts can be type checked, even when + /// they are disabled, which avoids them becoming stale (i.e. out of sync + /// with the codebase) over time. + /// + /// The optimiser should be able to eliminate all contract code guarded + /// by `if false`, leaving the original body intact when runtime contract + /// checks are disabled. pub(super) fn lower_contract( &mut self, body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>, @@ -14,14 +26,20 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // // into: // + // let __postcond = if contract_checks { + // contract_check_requires(PRECOND); + // Some(|ret_val| POSTCOND) + // } else { + // None + // }; // { - // let __postcond = if contracts_checks() { - // contract_check_requires(PRECOND); - // Some(|ret_val| POSTCOND) - // } else { - // None - // }; - // contract_check_ensures(__postcond, { body }) + // let ret = { body }; + // + // if contract_checks { + // contract_check_ensures(__postcond, ret) + // } else { + // ret + // } // } let precond = self.lower_precond(req); @@ -41,13 +59,19 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // // into: // + // let __postcond = if contract_checks { + // Some(|ret_val| POSTCOND) + // } else { + // None + // }; // { - // let __postcond = if contracts_check() { - // Some(|ret_val| POSTCOND) - // } else { - // None - // }; - // __postcond({ body }) + // let ret = { body }; + // + // if contract_checks { + // contract_check_ensures(__postcond, ret) + // } else { + // ret + // } // } let postcond_checker = self.lower_postcond_checker(ens); @@ -66,7 +90,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // into: // // { - // if contracts_check() { + // if contracts_checks { // contract_requires(PRECOND); // } // body @@ -129,11 +153,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let then_block = self.arena.alloc(self.expr_block(&then_block_stmts)); let precond_check = rustc_hir::ExprKind::If( - self.expr_call_lang_item_fn( - precond.span, - rustc_hir::LangItem::ContractChecks, - Default::default(), - ), + self.arena.alloc(self.expr_bool_literal(precond.span, self.tcx.sess.contract_checks())), then_block, None, ); @@ -170,11 +190,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let else_block = self.arena.alloc(self.expr_block(else_block)); let contract_check = rustc_hir::ExprKind::If( - self.expr_call_lang_item_fn( - span, - rustc_hir::LangItem::ContractChecks, - Default::default(), - ), + self.arena.alloc(self.expr_bool_literal(span, self.tcx.sess.contract_checks())), then_block, Some(else_block), ); @@ -249,12 +265,60 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { cond_ident: rustc_span::Ident, cond_hir_id: rustc_hir::HirId, ) -> &'hir rustc_hir::Expr<'hir> { + // { + // let ret = { body }; + // + // if contract_checks { + // contract_check_ensures(__postcond, ret) + // } else { + // ret + // } + // } + let ret_ident: rustc_span::Ident = rustc_span::Ident::from_str_and_span("__ret", span); + + // Set up the return `let` statement. + let (ret_pat, ret_hir_id) = + self.pat_ident_binding_mode_mut(span, ret_ident, rustc_hir::BindingMode::NONE); + + let ret_stmt = self.stmt_let_pat( + None, + span, + Some(expr), + self.arena.alloc(ret_pat), + rustc_hir::LocalSource::Contract, + ); + + let ret = self.expr_ident(span, ret_ident, ret_hir_id); + let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id); - let call_expr = self.expr_call_lang_item_fn_mut( + let contract_check = self.expr_call_lang_item_fn_mut( span, rustc_hir::LangItem::ContractCheckEnsures, - arena_vec![self; *cond_fn, *expr], + arena_vec![self; *cond_fn, *ret], ); - self.arena.alloc(call_expr) + let contract_check = self.arena.alloc(contract_check); + let call_expr = self.block_expr_block(contract_check); + + // same ident can't be used in 2 places, so we create a new one for the + // else branch + let ret = self.expr_ident(span, ret_ident, ret_hir_id); + let ret_block = self.block_expr_block(ret); + + let contracts_enabled: rustc_hir::Expr<'_> = + self.expr_bool_literal(span, self.tcx.sess.contract_checks()); + let contract_check = self.arena.alloc(self.expr( + span, + rustc_hir::ExprKind::If( + self.arena.alloc(contracts_enabled), + call_expr, + Some(ret_block), + ), + )); + + let attrs: rustc_ast::AttrVec = thin_vec![self.unreachable_code_attr(span)]; + self.lower_attrs(contract_check.hir_id, &attrs, span, rustc_hir::Target::Expression); + + let ret_block = self.block_all(span, arena_vec![self; ret_stmt], Some(contract_check)); + self.arena.alloc(self.expr_block(self.arena.alloc(ret_block))) } } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index d1ce3b169d75f..4a9b9f544b53d 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1941,16 +1941,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) }; - // `#[allow(unreachable_code)]` - let attr = attr::mk_attr_nested_word( - &self.tcx.sess.psess.attr_id_generator, - AttrStyle::Outer, - Safety::Default, - sym::allow, - sym::unreachable_code, - try_span, - ); - let attrs: AttrVec = thin_vec![attr]; + let attrs: AttrVec = thin_vec![self.unreachable_code_attr(try_span)]; // `ControlFlow::Continue(val) => #[allow(unreachable_code)] val,` let continue_arm = { @@ -2290,6 +2281,17 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(b.span, hir::ExprKind::Block(b, None)) } + /// Wrap an expression in a block, and wrap that block in an expression again. + /// Useful for constructing if-expressions, which require expressions of + /// kind block. + pub(super) fn block_expr_block( + &mut self, + expr: &'hir hir::Expr<'hir>, + ) -> &'hir hir::Expr<'hir> { + let b = self.block_expr(expr); + self.arena.alloc(self.expr_block(b)) + } + pub(super) fn expr_array_ref( &mut self, span: Span, @@ -2303,6 +2305,10 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, expr)) } + pub(super) fn expr_bool_literal(&mut self, span: Span, val: bool) -> hir::Expr<'hir> { + self.expr(span, hir::ExprKind::Lit(Spanned { node: LitKind::Bool(val), span })) + } + pub(super) fn expr(&mut self, span: Span, kind: hir::ExprKind<'hir>) -> hir::Expr<'hir> { let hir_id = self.next_id(); hir::Expr { hir_id, kind, span: self.lower_span(span) } @@ -2336,6 +2342,19 @@ impl<'hir> LoweringContext<'_, 'hir> { body: expr, } } + + /// `#[allow(unreachable_code)]` + pub(super) fn unreachable_code_attr(&mut self, span: Span) -> Attribute { + let attr = attr::mk_attr_nested_word( + &self.tcx.sess.psess.attr_id_generator, + AttrStyle::Outer, + Safety::Default, + sym::allow, + sym::unreachable_code, + span, + ); + attr + } } /// Used by [`LoweringContext::make_lowered_await`] to customize the desugaring based on what kind diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr deleted file mode 100644 index a60ce1602659b..0000000000000 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr +++ /dev/null @@ -1,11 +0,0 @@ -warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/contract-lang-items.rs:15:12 - | -LL | #![feature(contracts)] // to access core::contracts - | ^^^^^^^^^ - | - = note: see issue #128044 for more information - = note: `#[warn(incomplete_features)]` on by default - -warning: 1 warning emitted - From 2a5dac7682d4be4515d45d32666d14a6e8b899a1 Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Thu, 4 Sep 2025 08:24:00 +0100 Subject: [PATCH 05/24] Remove no longer used contract_checks intrinsic The contract_checks compiler flag is now used to determine if runtime contract checks should be enabled, as opposed to the compiler intrinsic as previously. --- compiler/rustc_hir/src/lang_items.rs | 1 - .../rustc_hir_analysis/src/check/intrinsic.rs | 2 -- .../src/lower_intrinsics.rs | 11 ---------- library/core/src/intrinsics/mod.rs | 18 ---------------- .../internal_machinery/contract-intrinsics.rs | 21 ++++--------------- .../contract-lang-items.chk_fail_post.stderr | 2 +- .../contract-lang-items.chk_pass.stderr | 2 +- .../internal_machinery/contract-lang-items.rs | 19 ++++------------- .../contract-lang-items.unchk_pass.stderr | 2 +- .../internal-feature-gating.rs | 2 -- .../internal-feature-gating.stderr | 20 +++++------------- 11 files changed, 16 insertions(+), 84 deletions(-) diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index d62ec5482b430..2e099a97b65be 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -429,7 +429,6 @@ language_item_table! { // Experimental lang items for implementing contract pre- and post-condition checking. ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None; ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None; - ContractChecks, sym::contract_checks, contract_checks_fn, Target::Fn, GenericRequirement::None; // Experimental lang items for `MCP: Low level components for async drop`(https://github.com/rust-lang/compiler-team/issues/727) DefaultTrait4, sym::default_trait4, default_trait4_trait, Target::Trait, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 5fd71fd1cd76c..a6659912e3fb9 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -647,8 +647,6 @@ pub(crate) fn check_intrinsic_type( sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))), - // contract_checks() -> bool - sym::contract_checks => (0, 0, Vec::new(), tcx.types.bool), // contract_check_requires::(C) -> bool, where C: impl Fn() -> bool sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit), sym::contract_check_ensures => { diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 8dadce0d448d3..fc8092fd5832b 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -34,17 +34,6 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics { )); terminator.kind = TerminatorKind::Goto { target }; } - sym::contract_checks => { - let target = target.unwrap(); - block.statements.push(Statement::new( - terminator.source_info, - StatementKind::Assign(Box::new(( - *destination, - Rvalue::NullaryOp(NullOp::ContractChecks, tcx.types.bool), - ))), - )); - terminator.kind = TerminatorKind::Goto { target }; - } sym::forget => { let target = target.unwrap(); block.statements.push(Statement::new( diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 0d3be136d1325..c397e762d5589 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2631,24 +2631,6 @@ pub const unsafe fn const_make_global(ptr: *mut u8) -> *const u8 { ptr } -/// Returns whether we should perform contract-checking at runtime. -/// -/// This is meant to be similar to the ub_checks intrinsic, in terms -/// of not prematurely committing at compile-time to whether contract -/// checking is turned on, so that we can specify contracts in libstd -/// and let an end user opt into turning them on. -#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] -#[rustc_const_unstable(feature = "contracts", issue = "128044")] -#[inline(always)] -#[lang = "contract_checks"] -#[rustc_intrinsic] -pub const fn contract_checks() -> bool { - // FIXME: should this be `false` or `cfg!(contract_checks)`? - - // cfg!(contract_checks) - false -} - /// Check if the pre-condition `cond` has been met. /// /// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition diff --git a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs index 5caea2b23d93c..9dd5167c9e117 100644 --- a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -1,29 +1,16 @@ -//@ revisions: default unchk_pass chk_pass chk_fail_ensures chk_fail_requires +//@ revisions: default chk_fail_ensures chk_fail_requires // //@ [default] run-pass -//@ [unchk_pass] run-pass -//@ [chk_pass] run-pass //@ [chk_fail_requires] run-crash //@ [chk_fail_ensures] run-crash -// -//@ [unchk_pass] compile-flags: -Zcontract-checks=no -//@ [chk_pass] compile-flags: -Zcontract-checks=yes -//@ [chk_fail_requires] compile-flags: -Zcontract-checks=yes -//@ [chk_fail_ensures] compile-flags: -Zcontract-checks=yes -#![feature(cfg_contract_checks, contracts_internals, core_intrinsics)] +#![feature(contracts_internals, core_intrinsics)] fn main() { - #[cfg(any(default, unchk_pass))] // default: disabled - assert_eq!(core::intrinsics::contract_checks(), false); - - #[cfg(chk_pass)] // explicitly enabled - assert_eq!(core::intrinsics::contract_checks(), true); - // always pass core::intrinsics::contract_check_requires(|| true); // always fail - #[cfg(any(chk_fail_requires))] + #[cfg(chk_fail_requires)] core::intrinsics::contract_check_requires(|| false); let doubles_to_two = { let old = 2; move |ret: &u32 | ret + ret == old }; @@ -31,6 +18,6 @@ fn main() { core::intrinsics::contract_check_ensures(Some(doubles_to_two), 1); // always fail - #[cfg(any(chk_fail_ensures))] + #[cfg(chk_fail_ensures)] core::intrinsics::contract_check_ensures(Some(doubles_to_two), 2); } diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr index a60ce1602659b..acce6b1fbc729 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr @@ -1,5 +1,5 @@ warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/contract-lang-items.rs:15:12 + --> $DIR/contract-lang-items.rs:8:12 | LL | #![feature(contracts)] // to access core::contracts | ^^^^^^^^^ diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr index a60ce1602659b..acce6b1fbc729 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr @@ -1,5 +1,5 @@ warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/contract-lang-items.rs:15:12 + --> $DIR/contract-lang-items.rs:8:12 | LL | #![feature(contracts)] // to access core::contracts | ^^^^^^^^^ diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs index 50cf592ac564d..ad88ebfe22e3b 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.rs +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -1,27 +1,16 @@ -//@ revisions: unchk_pass unchk_fail_post chk_pass chk_fail_post +//@ revisions: unchk_pass chk_pass chk_fail_post // //@ [unchk_pass] run-pass -//@ [unchk_fail_post] run-pass //@ [chk_pass] run-pass // //@ [chk_fail_post] run-crash -// -//@ [unchk_pass] compile-flags: -Zcontract-checks=no -//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no -// -//@ [chk_pass] compile-flags: -Zcontract-checks=yes -//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes #![feature(contracts)] // to access core::contracts //~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] #![feature(contracts_internals)] // to access check_requires lang item #![feature(core_intrinsics)] fn foo(x: Baz) -> i32 { - let injected_checker = if core::intrinsics::contract_checks() { - Some(core::contracts::build_check_ensures(|ret| *ret > 100)) - } else { - None - }; + let injected_checker = Some(core::contracts::build_check_ensures(|ret| *ret > 100)); let ret = x.baz + 50; core::intrinsics::contract_check_ensures(injected_checker, ret) @@ -31,11 +20,11 @@ struct Baz { baz: i32 } const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; -#[cfg(any(unchk_fail_post, chk_fail_post))] +#[cfg(chk_fail_post)] const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; fn main() { assert_eq!(foo(BAZ_PASS_PRE_POST), 150); - #[cfg(any(unchk_fail_post, chk_fail_post))] + #[cfg(chk_fail_post)] foo(BAZ_FAIL_POST); } diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr index a60ce1602659b..acce6b1fbc729 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr @@ -1,5 +1,5 @@ warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/contract-lang-items.rs:15:12 + --> $DIR/contract-lang-items.rs:8:12 | LL | #![feature(contracts)] // to access core::contracts | ^^^^^^^^^ diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs index d101ab33547eb..48bd376594f22 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -2,8 +2,6 @@ fn main() { // intrinsics are guarded by contracts_internals feature gate. - core::intrinsics::contract_checks(); - //~^ ERROR use of unstable library feature `contracts_internals` core::intrinsics::contract_check_requires(|| true); //~^ ERROR use of unstable library feature `contracts_internals` core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr index 738b7a3e09ff1..c1318fc15a78d 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -1,5 +1,5 @@ error[E0658]: contract internal machinery is for internal use only - --> $DIR/internal-feature-gating.rs:16:28 + --> $DIR/internal-feature-gating.rs:14:28 | LL | fn identity_1() -> i32 contract_requires(|| true) { 10 } | ^^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | fn identity_1() -> i32 contract_requires(|| true) { 10 } = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: contract internal machinery is for internal use only - --> $DIR/internal-feature-gating.rs:18:28 + --> $DIR/internal-feature-gating.rs:16:28 | LL | fn identity_2() -> i32 contract_ensures(|_| true) { 10 } | ^^^^^^^^^^^^^^^^ @@ -21,16 +21,6 @@ LL | fn identity_2() -> i32 contract_ensures(|_| true) { 10 } error[E0658]: use of unstable library feature `contracts_internals` --> $DIR/internal-feature-gating.rs:5:5 | -LL | core::intrinsics::contract_checks(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #128044 for more information - = help: add `#![feature(contracts_internals)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: use of unstable library feature `contracts_internals` - --> $DIR/internal-feature-gating.rs:7:5 - | LL | core::intrinsics::contract_check_requires(|| true); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | @@ -39,7 +29,7 @@ LL | core::intrinsics::contract_check_requires(|| true); = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: use of unstable library feature `contracts_internals` - --> $DIR/internal-feature-gating.rs:9:5 + --> $DIR/internal-feature-gating.rs:7:5 | LL | core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +39,7 @@ LL | core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: use of unstable library feature `contracts_internals` - --> $DIR/internal-feature-gating.rs:12:5 + --> $DIR/internal-feature-gating.rs:10:5 | LL | core::contracts::build_check_ensures(|_: &()| true); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -58,6 +48,6 @@ LL | core::contracts::build_check_ensures(|_: &()| true); = help: add `#![feature(contracts_internals)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 6 previous errors +error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0658`. From a172a66ae8a533ed3797326aca25f0a4beae2013 Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Thu, 4 Sep 2025 09:01:14 +0100 Subject: [PATCH 06/24] Wrap contract clauses in brances instead of parenthesis The compiler complained about uncecessary parenthesis on contract clauses, which were insterted by the contract macros. This commit changes the macro to use braces as the delimiter instead, fixing the issue. --- compiler/rustc_builtin_macros/src/contracts.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs index 6a24af361fe78..85fbe934124f5 100644 --- a/compiler/rustc_builtin_macros/src/contracts.rs +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -141,7 +141,7 @@ fn expand_requires_tts( new_tts.push_tree(TokenTree::Delimited( DelimSpan::from_single(attr_span), DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), - token::Delimiter::Parenthesis, + token::Delimiter::Brace, annotation, )); Ok(()) @@ -163,7 +163,7 @@ fn expand_ensures_tts( new_tts.push_tree(TokenTree::Delimited( DelimSpan::from_single(attr_span), DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), - token::Delimiter::Parenthesis, + token::Delimiter::Brace, annotation, )); Ok(()) From 4d23c4135e3d62e1429bce1e851ad7672626bf6e Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Sun, 12 Oct 2025 18:04:38 +0100 Subject: [PATCH 07/24] Test cross-crate runtime contract checks These tests capture the behaviour that the decision to include/exclude runtime contract assertions is determined on a per-crate basis, i.e. by the flags used to compile each crate. --- .../cross-crate-checks/auxiliary/id.rs | 16 ++++++++++++ .../cross-crate-checks/cross-crate.rs | 26 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/ui/contracts/cross-crate-checks/auxiliary/id.rs create mode 100644 tests/ui/contracts/cross-crate-checks/cross-crate.rs diff --git a/tests/ui/contracts/cross-crate-checks/auxiliary/id.rs b/tests/ui/contracts/cross-crate-checks/auxiliary/id.rs new file mode 100644 index 0000000000000..c6210375a8482 --- /dev/null +++ b/tests/ui/contracts/cross-crate-checks/auxiliary/id.rs @@ -0,0 +1,16 @@ +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail] compile-flags: -Zcontract-checks=yes +//@ [chk_pass] compile-flags: -Zcontract-checks=no +//@ [chk_fail] compile-flags: -Zcontract-checks=yes + +#![crate_type = "lib"] +#![feature(contracts)] + +extern crate core; +use core::contracts::requires; + +/// Example function with a spec to be called across a crate boundary. +#[requires(x > 0)] +pub fn id_if_positive(x: u32) -> u32 { + x +} diff --git a/tests/ui/contracts/cross-crate-checks/cross-crate.rs b/tests/ui/contracts/cross-crate-checks/cross-crate.rs new file mode 100644 index 0000000000000..43f81fe0d807a --- /dev/null +++ b/tests/ui/contracts/cross-crate-checks/cross-crate.rs @@ -0,0 +1,26 @@ +//@ aux-build:id.rs +//@ revisions: unchk_pass unchk_fail chk_pass chk_fail +// +// The dependency crate `id` can be compiled with runtime contract checking +// enabled independently of whether this crate is compiled with contract checks +// or not. +// +// chk/unchk indicates whether this crate is compiled with contracts or not +// and pass/fail indicates whether the `id` crate is compiled with contract checks. +// +//@ [unchk_pass] run-pass +//@ [unchk_fail] run-crash +//@ [chk_pass] run-pass +//@ [chk_fail] run-crash +// +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail] compile-flags: -Zcontract-checks=no +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail] compile-flags: -Zcontract-checks=yes + +extern crate id; + +fn main() { + id::id_if_positive(0); +} From 12c0d697d7ba338783ef43e06e866a8b3b680112 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 13 Oct 2025 14:00:22 +0200 Subject: [PATCH 08/24] use check_nondet helper in a few more places --- .../miri/tests/pass-dep/libc/libc-mem.rs | 11 +++++---- src/tools/miri/tests/pass/dyn-traits.rs | 6 ++++- .../miri/tests/pass/function_pointers.rs | 7 +++++- src/tools/miri/tests/pass/shims/fs.rs | 23 ++++++++++--------- src/tools/miri/tests/utils/mod.rs | 5 +++- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-mem.rs b/src/tools/miri/tests/pass-dep/libc/libc-mem.rs index 531d637d1f24d..7105b076d652d 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-mem.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-mem.rs @@ -1,6 +1,10 @@ #![feature(pointer_is_aligned_to)] use std::{mem, ptr, slice}; +#[path = "../../utils/mod.rs"] +mod utils; +use utils::check_nondet; + fn test_memcpy() { unsafe { let src = [1i8, 2, 3]; @@ -120,13 +124,12 @@ fn test_memset() { } fn test_malloc() { - // Test that small allocations sometimes *are* not very aligned. - let saw_unaligned = (0..64).any(|_| unsafe { + // Test that small allocations sometimes are *not* very aligned (and sometimes they are). + check_nondet(|| unsafe { let p = libc::malloc(3); libc::free(p); - (p as usize) % 4 != 0 // find any that this is *not* 4-aligned + (p as usize) % 4 == 0 }); - assert!(saw_unaligned); unsafe { let p1 = libc::malloc(20); diff --git a/src/tools/miri/tests/pass/dyn-traits.rs b/src/tools/miri/tests/pass/dyn-traits.rs index f6220120968f7..47f913adae053 100644 --- a/src/tools/miri/tests/pass/dyn-traits.rs +++ b/src/tools/miri/tests/pass/dyn-traits.rs @@ -1,3 +1,7 @@ +#[path = "../utils/mod.rs"] +mod utils; +use utils::check_nondet; + fn ref_box_dyn() { struct Struct(i32); @@ -147,7 +151,7 @@ fn vtable_ptr_eq() { // We don't always get the same vtable when casting this to a wide pointer. let x = &2; let x_wide = x as &dyn fmt::Display; - assert!((0..256).any(|_| !ptr::eq(x as &dyn fmt::Display, x_wide))); + check_nondet(|| ptr::eq(x as &dyn fmt::Display, x_wide)); } fn main() { diff --git a/src/tools/miri/tests/pass/function_pointers.rs b/src/tools/miri/tests/pass/function_pointers.rs index 7145be334a13e..96f6f2568e98e 100644 --- a/src/tools/miri/tests/pass/function_pointers.rs +++ b/src/tools/miri/tests/pass/function_pointers.rs @@ -2,6 +2,10 @@ use std::mem; +#[path = "../utils/mod.rs"] +mod utils; +use utils::check_nondet; + trait Answer { fn answer() -> Self; } @@ -30,6 +34,7 @@ fn i() -> i32 { 73 } +#[inline(never)] // optimizations mask the non-determinism we test for below fn return_fn_ptr(f: fn() -> i32) -> fn() -> i32 { f } @@ -85,7 +90,7 @@ fn main() { assert!(return_fn_ptr(i) as unsafe fn() -> i32 == i as fn() -> i32 as unsafe fn() -> i32); // Miri gives different addresses to different reifications of a generic function. // at least if we try often enough. - assert!((0..256).any(|_| return_fn_ptr(f) != f)); + check_nondet(|| return_fn_ptr(f) == f); // However, if we only turn `f` into a function pointer and use that pointer, // it is equal to itself. let f2 = f as fn() -> i32; diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 022dcc5dcbafa..43fbf6b085f75 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -13,6 +13,7 @@ use std::path::Path; #[path = "../../utils/mod.rs"] mod utils; +use utils::check_nondet; fn main() { test_path_conversion(); @@ -81,25 +82,25 @@ fn test_file() { } fn test_file_partial_reads_writes() { - let path = utils::prepare_with_content("miri_test_fs_file.txt", b"abcdefg"); + let path1 = utils::prepare_with_content("miri_test_fs_file1.txt", b"abcdefg"); + let path2 = utils::prepare_with_content("miri_test_fs_file2.txt", b"abcdefg"); // Ensure we sometimes do incomplete writes. - let got_short_write = (0..16).any(|_| { - let _ = remove_file(&path); // FIXME(win, issue #4483): errors if the file already exists - let mut file = File::create(&path).unwrap(); - file.write(&[0; 4]).unwrap() != 4 + check_nondet(|| { + let _ = remove_file(&path1); // FIXME(win, issue #4483): errors if the file already exists + let mut file = File::create(&path1).unwrap(); + file.write(&[0; 4]).unwrap() == 4 }); - assert!(got_short_write); // Ensure we sometimes do incomplete reads. - let got_short_read = (0..16).any(|_| { - let mut file = File::open(&path).unwrap(); + check_nondet(|| { + let mut file = File::open(&path2).unwrap(); let mut buf = [0; 4]; - file.read(&mut buf).unwrap() != 4 + file.read(&mut buf).unwrap() == 4 }); - assert!(got_short_read); // Clean up - remove_file(&path).unwrap(); + remove_file(&path1).unwrap(); + remove_file(&path2).unwrap(); } fn test_file_clone() { diff --git a/src/tools/miri/tests/utils/mod.rs b/src/tools/miri/tests/utils/mod.rs index 37f9996216343..f855f115e43dc 100644 --- a/src/tools/miri/tests/utils/mod.rs +++ b/src/tools/miri/tests/utils/mod.rs @@ -55,7 +55,10 @@ pub fn check_all_outcomes( /// Check that the operation is non-deterministic #[track_caller] pub fn check_nondet(f: impl Fn() -> T) { - let rounds = 50; + // We test some rather unlikely events with this, such as two global allocations getting the + // same "salt" (1/32 chance). So give this *many* shots before we consider the test to have + // failed. + let rounds = 500; let first = f(); for _ in 1..rounds { if f() != first { From fe91820130aa4e1b3c36998dd89b1ee04777d0d7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 14 Oct 2025 21:24:22 +0200 Subject: [PATCH 09/24] native-lib: support all types with Scalar layout --- src/tools/miri/src/diagnostics.rs | 13 ++++ src/tools/miri/src/shims/native_lib/mod.rs | 65 ++++++++++++------- .../pass/ptr_read_access.notrace.stderr | 15 +++++ .../tests/native-lib/pass/ptr_read_access.rs | 20 ++++++ .../pass/ptr_read_access.trace.stderr | 15 +++++ .../miri/tests/native-lib/ptr_read_access.c | 13 ++++ 6 files changed, 117 insertions(+), 24 deletions(-) diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index d3486dcbb19c9..1c3de9035cf86 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -139,6 +139,7 @@ pub enum NonHaltingDiagnostic { NativeCallSharedMem { tracing: bool, }, + NativeCallFnPtr, WeakMemoryOutdatedLoad { ptr: Pointer, }, @@ -644,6 +645,11 @@ impl<'tcx> MiriMachine<'tcx> { Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning), NativeCallSharedMem { .. } => ("sharing memory with a native function".to_string(), DiagLevel::Warning), + NativeCallFnPtr => + ( + "sharing a function pointer with a native function".to_string(), + DiagLevel::Warning, + ), ExternTypeReborrow => ("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning), GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } => @@ -682,6 +688,8 @@ impl<'tcx> MiriMachine<'tcx> { Int2Ptr { .. } => format!("integer-to-pointer cast"), NativeCallSharedMem { .. } => format!("sharing memory with a native function called via FFI"), + NativeCallFnPtr => + format!("sharing a function pointer with a native function called via FFI"), WeakMemoryOutdatedLoad { ptr } => format!("weak memory emulation: outdated value returned from load at {ptr}"), ExternTypeReborrow => @@ -779,6 +787,11 @@ impl<'tcx> MiriMachine<'tcx> { ), ] }, + NativeCallFnPtr => { + vec![note!( + "calling Rust functions from C is not supported and will, in the best case, crash the program" + )] + } ExternTypeReborrow => { assert!(self.borrow_tracker.as_ref().is_some_and(|b| { matches!( diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs index 47102c30bc3b1..eb473de487408 100644 --- a/src/tools/miri/src/shims/native_lib/mod.rs +++ b/src/tools/miri/src/shims/native_lib/mod.rs @@ -6,7 +6,7 @@ use std::sync::atomic::AtomicBool; use libffi::low::CodePtr; use libffi::middle::Type as FfiType; use rustc_abi::{HasDataLayout, Size}; -use rustc_middle::ty::layout::HasTypingEnv; +use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, IntTy, Ty, UintTy}; use rustc_span::Symbol; use serde::{Deserialize, Serialize}; @@ -277,7 +277,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // This should go first so that we emit unsupported before doing a bunch // of extra work for types that aren't supported yet. - let ty = this.ty_to_ffitype(v.layout.ty)?; + let ty = this.ty_to_ffitype(v.layout)?; // Helper to print a warning when a pointer is shared with the native code. let expose = |prov: Provenance| -> InterpResult<'tcx> { @@ -386,34 +386,44 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_ref(); let mut fields = vec![]; for field in &adt_def.non_enum_variant().fields { - fields.push(this.ty_to_ffitype(field.ty(*this.tcx, args))?); + let layout = this.layout_of(field.ty(*this.tcx, args))?; + fields.push(this.ty_to_ffitype(layout)?); } interp_ok(FfiType::structure(fields)) } /// Gets the matching libffi type for a given Ty. - fn ty_to_ffitype(&self, ty: Ty<'tcx>) -> InterpResult<'tcx, FfiType> { - let this = self.eval_context_ref(); - interp_ok(match ty.kind() { - ty::Int(IntTy::I8) => FfiType::i8(), - ty::Int(IntTy::I16) => FfiType::i16(), - ty::Int(IntTy::I32) => FfiType::i32(), - ty::Int(IntTy::I64) => FfiType::i64(), - ty::Int(IntTy::Isize) => FfiType::isize(), - ty::Uint(UintTy::U8) => FfiType::u8(), - ty::Uint(UintTy::U16) => FfiType::u16(), - ty::Uint(UintTy::U32) => FfiType::u32(), - ty::Uint(UintTy::U64) => FfiType::u64(), - ty::Uint(UintTy::Usize) => FfiType::usize(), - ty::RawPtr(pointee_ty, _mut) => { - if !pointee_ty.is_sized(*this.tcx, this.typing_env()) { - throw_unsup_format!("passing a pointer to an unsized type over FFI: {}", ty); - } - FfiType::pointer() - } - ty::Adt(adt_def, args) => self.adt_to_ffitype(ty, *adt_def, args)?, - _ => throw_unsup_format!("unsupported argument type for native call: {}", ty), + fn ty_to_ffitype(&self, layout: TyAndLayout<'tcx>) -> InterpResult<'tcx, FfiType> { + use rustc_abi::{AddressSpace, BackendRepr, Integer, Primitive}; + + // `BackendRepr::Scalar` is also a signal to pass this type as a scalar in the ABI. This + // matches what codegen does. This does mean that we support some types whose ABI is not + // stable, but that's fine -- we are anyway quite conservative in native-lib mode. + if let BackendRepr::Scalar(s) = layout.backend_repr { + // Simple sanity-check: this cannot be `repr(C)`. + assert!(!layout.ty.ty_adt_def().is_some_and(|adt| adt.repr().c())); + return interp_ok(match s.primitive() { + Primitive::Int(Integer::I8, /* signed */ true) => FfiType::i8(), + Primitive::Int(Integer::I16, /* signed */ true) => FfiType::i16(), + Primitive::Int(Integer::I32, /* signed */ true) => FfiType::i32(), + Primitive::Int(Integer::I64, /* signed */ true) => FfiType::i64(), + Primitive::Int(Integer::I8, /* signed */ false) => FfiType::u8(), + Primitive::Int(Integer::I16, /* signed */ false) => FfiType::u16(), + Primitive::Int(Integer::I32, /* signed */ false) => FfiType::u32(), + Primitive::Int(Integer::I64, /* signed */ false) => FfiType::u64(), + Primitive::Pointer(AddressSpace::ZERO) => FfiType::pointer(), + _ => + throw_unsup_format!( + "unsupported scalar argument type for native call: {}", + layout.ty + ), + }); + } + interp_ok(match layout.ty.kind() { + // Scalar types have already been handled above. + ty::Adt(adt_def, args) => self.adt_to_ffitype(layout.ty, *adt_def, args)?, + _ => throw_unsup_format!("unsupported argument type for native call: {}", layout.ty), }) } } @@ -454,6 +464,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // pointer was passed as argument). Uninitialised memory is left as-is, but any data // exposed this way is garbage anyway. this.visit_reachable_allocs(this.exposed_allocs(), |this, alloc_id, info| { + if matches!(info.kind, AllocKind::Function) { + static DEDUP: AtomicBool = AtomicBool::new(false); + if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) { + // Newly set, so first time we get here. + this.emit_diagnostic(NonHaltingDiagnostic::NativeCallFnPtr); + } + } // If there is no data behind this pointer, skip this. if !matches!(info.kind, AllocKind::LiveData) { return interp_ok(()); diff --git a/src/tools/miri/tests/native-lib/pass/ptr_read_access.notrace.stderr b/src/tools/miri/tests/native-lib/pass/ptr_read_access.notrace.stderr index 04a3025baefa9..200eba8050bb5 100644 --- a/src/tools/miri/tests/native-lib/pass/ptr_read_access.notrace.stderr +++ b/src/tools/miri/tests/native-lib/pass/ptr_read_access.notrace.stderr @@ -16,3 +16,18 @@ note: inside `main` LL | test_access_pointer(); | ^^^^^^^^^^^^^^^^^^^^^ +warning: sharing a function pointer with a native function called via FFI + --> tests/native-lib/pass/ptr_read_access.rs:LL:CC + | +LL | pass_fn_ptr(Some(nop)); // this one is not + | ^^^^^^^^^^^^^^^^^^^^^^ sharing a function pointer with a native function + | + = help: calling Rust functions from C is not supported and will, in the best case, crash the program + = note: BACKTRACE: + = note: inside `pass_fn_ptr` at tests/native-lib/pass/ptr_read_access.rs:LL:CC +note: inside `main` + --> tests/native-lib/pass/ptr_read_access.rs:LL:CC + | +LL | pass_fn_ptr(); + | ^^^^^^^^^^^^^ + diff --git a/src/tools/miri/tests/native-lib/pass/ptr_read_access.rs b/src/tools/miri/tests/native-lib/pass/ptr_read_access.rs index bab73f7cf1782..36eff04a03c05 100644 --- a/src/tools/miri/tests/native-lib/pass/ptr_read_access.rs +++ b/src/tools/miri/tests/native-lib/pass/ptr_read_access.rs @@ -3,11 +3,14 @@ //@[trace] compile-flags: -Zmiri-native-lib-enable-tracing //@compile-flags: -Zmiri-permissive-provenance +use std::ptr::NonNull; + fn main() { test_access_pointer(); test_access_simple(); test_access_nested(); test_access_static(); + pass_fn_ptr(); } /// Test function that dereferences an int pointer and prints its contents from C. @@ -30,11 +33,15 @@ fn test_access_simple() { extern "C" { fn access_simple(s_ptr: *const Simple) -> i32; + fn access_simple2(s_ptr: NonNull) -> i32; + fn access_simple3(s_ptr: Option>) -> i32; } let simple = Simple { field: -42 }; assert_eq!(unsafe { access_simple(&simple) }, -42); + assert_eq!(unsafe { access_simple2(NonNull::from(&simple)) }, -42); + assert_eq!(unsafe { access_simple3(Some(NonNull::from(&simple))) }, -42); } /// Test function that dereferences nested struct pointers and accesses fields. @@ -75,3 +82,16 @@ fn test_access_static() { assert_eq!(unsafe { access_static(&STATIC) }, 9001); } + +fn pass_fn_ptr() { + extern "C" { + fn pass_fn_ptr(s: Option); + } + + extern "C" fn nop() {} + + unsafe { + pass_fn_ptr(None); // this one is fine + pass_fn_ptr(Some(nop)); // this one is not + } +} diff --git a/src/tools/miri/tests/native-lib/pass/ptr_read_access.trace.stderr b/src/tools/miri/tests/native-lib/pass/ptr_read_access.trace.stderr index c2a4508b7fcc7..5c0e954deb9b6 100644 --- a/src/tools/miri/tests/native-lib/pass/ptr_read_access.trace.stderr +++ b/src/tools/miri/tests/native-lib/pass/ptr_read_access.trace.stderr @@ -17,3 +17,18 @@ note: inside `main` LL | test_access_pointer(); | ^^^^^^^^^^^^^^^^^^^^^ +warning: sharing a function pointer with a native function called via FFI + --> tests/native-lib/pass/ptr_read_access.rs:LL:CC + | +LL | pass_fn_ptr(Some(nop)); // this one is not + | ^^^^^^^^^^^^^^^^^^^^^^ sharing a function pointer with a native function + | + = help: calling Rust functions from C is not supported and will, in the best case, crash the program + = note: BACKTRACE: + = note: inside `pass_fn_ptr` at tests/native-lib/pass/ptr_read_access.rs:LL:CC +note: inside `main` + --> tests/native-lib/pass/ptr_read_access.rs:LL:CC + | +LL | pass_fn_ptr(); + | ^^^^^^^^^^^^^ + diff --git a/src/tools/miri/tests/native-lib/ptr_read_access.c b/src/tools/miri/tests/native-lib/ptr_read_access.c index 021eb6adca4f6..5f071ca3d424d 100644 --- a/src/tools/miri/tests/native-lib/ptr_read_access.c +++ b/src/tools/miri/tests/native-lib/ptr_read_access.c @@ -19,6 +19,13 @@ typedef struct Simple { EXPORT int32_t access_simple(const Simple *s_ptr) { return s_ptr->field; } +// Some copies so Rust can import them at different types. +EXPORT int32_t access_simple2(const Simple *s_ptr) { + return s_ptr->field; +} +EXPORT int32_t access_simple3(const Simple *s_ptr) { + return s_ptr->field; +} /* Test: test_access_nested */ @@ -55,3 +62,9 @@ EXPORT int32_t access_static(const Static *s_ptr) { EXPORT uintptr_t do_one_deref(const int32_t ***ptr) { return (uintptr_t)*ptr; } + +/* Test: pass_fn_ptr */ + +EXPORT void pass_fn_ptr(void f(void)) { + (void)f; // suppress unused warning +} From 2feb8bba402107dcfea07e875aabf309a8c6f217 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 14 Oct 2025 23:07:25 +0200 Subject: [PATCH 10/24] better error when native code tries to execute Rust function --- src/tools/miri/src/shims/native_lib/trace/parent.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/native_lib/trace/parent.rs b/src/tools/miri/src/shims/native_lib/trace/parent.rs index 3ae98259ab35b..335188b331838 100644 --- a/src/tools/miri/src/shims/native_lib/trace/parent.rs +++ b/src/tools/miri/src/shims/native_lib/trace/parent.rs @@ -530,7 +530,18 @@ fn handle_segfault( // Don't use wait_for_signal here since 1 instruction doesn't give room // for any uncertainty + we don't want it `cont()`ing randomly by accident // Also, don't let it continue with unprotected memory if something errors! - let _ = wait::waitid(wait::Id::Pid(pid), WAIT_FLAGS).map_err(|_| ExecEnd(None))?; + let stat = wait::waitid(wait::Id::Pid(pid), WAIT_FLAGS).map_err(|_| ExecEnd(None))?; + match stat { + wait::WaitStatus::Signaled(_, s, _) + | wait::WaitStatus::Stopped(_, s) + | wait::WaitStatus::PtraceEvent(_, s, _) => + assert!( + !matches!(s, signal::SIGSEGV), + "native code segfaulted when re-trying memory access\n\ + is the native code trying to call a Rust function?" + ), + _ => (), + } // Zero out again to be safe for a in (ch_stack..ch_stack.strict_add(CALLBACK_STACK_SIZE)).step_by(ARCH_WORD_SIZE) { From 75b60f17ea74aa0b47ebd9b4c03e496f9238297d Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 15 Oct 2025 04:53:14 +0000 Subject: [PATCH 11/24] Prepare for merging from rust-lang/rust This updates the rust-version file to 235a4c083eb2a2bfe8779d211c3232f39396de00. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index f877706520ea1..f4bdbbff8ea7c 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -36e4f5d1fe1d63953a5bf1758ce2b64172623e2e +235a4c083eb2a2bfe8779d211c3232f39396de00 From a9fc0ca17629aa73760b1315db64fbd8b199ecc4 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 15 Oct 2025 05:01:36 +0000 Subject: [PATCH 12/24] fmt --- src/tools/miri/src/shims/unix/foreign_items.rs | 4 +++- src/tools/miri/src/shims/unix/macos/foreign_items.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index d04ef5eac541b..1f8e60484edaa 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -815,7 +815,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "pthread_cond_timedwait" => { let [cond, mutex, abstime] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - this.pthread_cond_timedwait(cond, mutex, abstime, dest, /* macos_relative_np */ false)?; + this.pthread_cond_timedwait( + cond, mutex, abstime, dest, /* macos_relative_np */ false, + )?; } "pthread_cond_destroy" => { let [cond] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 0754eb45a2d29..1b273593de63e 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -310,7 +310,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "pthread_cond_timedwait_relative_np" => { let [cond, mutex, reltime] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - this.pthread_cond_timedwait(cond, mutex, reltime, dest, /* macos_relative_np */ true)?; + this.pthread_cond_timedwait( + cond, mutex, reltime, dest, /* macos_relative_np */ true, + )?; } _ => return interp_ok(EmulateItemResult::NotSupported), From 1819f5eecef48bd57edbad9e064c9b90658a6afc Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 15 Oct 2025 08:15:12 +0200 Subject: [PATCH 13/24] don't debug-print an Any --- src/tools/miri/src/shims/native_lib/trace/child.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/shims/native_lib/trace/child.rs b/src/tools/miri/src/shims/native_lib/trace/child.rs index 95b0617a0261b..795ad4a32076d 100644 --- a/src/tools/miri/src/shims/native_lib/trace/child.rs +++ b/src/tools/miri/src/shims/native_lib/trace/child.rs @@ -228,9 +228,10 @@ pub unsafe fn init_sv() -> Result<(), SvInitError> { match init { // The "Ok" case means that we couldn't ptrace. Ok(e) => return Err(e), - Err(p) => { + Err(_p) => { eprintln!( - "Supervisor process panicked!\n{p:?}\n\nTry running again without using the native-lib tracer." + "Supervisor process panicked!\n\" + Try running again without `-Zmiri-native-lib-enable-tracing`." ); std::process::exit(1); } From 7c419e86232596ced25286efb70839b7fbabb6bb Mon Sep 17 00:00:00 2001 From: Nia Espera Date: Wed, 15 Oct 2025 13:22:05 +0200 Subject: [PATCH 14/24] native-lib: bump libffi --- src/tools/miri/Cargo.lock | 8 +++---- src/tools/miri/Cargo.toml | 2 +- src/tools/miri/src/shims/native_lib/ffi.rs | 25 +++++++--------------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 8792d90ac5366..d66529a464b69 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -794,9 +794,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libffi" -version = "4.1.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7681c6fab541f799a829e44a445a0666cf8d8a6cfebf89419e6aed52c604e87" +checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5" dependencies = [ "libc", "libffi-sys", @@ -804,9 +804,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "3.3.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0d828d367b4450ed08e7d510dc46636cd660055f50d67ac943bfe788767c29" +checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7" dependencies = [ "cc", ] diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 12123c260da0a..8bb4f1c160934 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -39,7 +39,7 @@ features = ['unprefixed_malloc_on_supported_platforms'] [target.'cfg(unix)'.dependencies] libc = "0.2" # native-lib dependencies -libffi = { version = "4.1.1", optional = true } +libffi = { version = "5.0.0", optional = true } libloading = { version = "0.8", optional = true } serde = { version = "1.0.219", features = ["derive"], optional = true } diff --git a/src/tools/miri/src/shims/native_lib/ffi.rs b/src/tools/miri/src/shims/native_lib/ffi.rs index 0badf22bb7651..7448b6601959f 100644 --- a/src/tools/miri/src/shims/native_lib/ffi.rs +++ b/src/tools/miri/src/shims/native_lib/ffi.rs @@ -9,11 +9,14 @@ use libffi::middle::{Arg as ArgPtr, Cif, Type as FfiType}; /// /// The safety invariants of the foreign function being called must be upheld (if any). pub unsafe fn call(fun: CodePtr, args: &mut [OwnedArg]) -> R { - let arg_ptrs: Vec<_> = args.iter().map(|arg| arg.ptr()).collect(); - let cif = Cif::new(args.iter_mut().map(|arg| arg.ty.take().unwrap()), R::reify().into_middle()); - // SAFETY: Caller upholds that the function is safe to call, and since we - // were passed a slice reference we know the `OwnedArg`s won't have been - // dropped by this point. + let mut cif_args = vec![]; + let mut arg_ptrs = vec![]; + for a in args { + cif_args.push(a.ty.take().unwrap()); + arg_ptrs.push(ArgPtr::new(&*a.bytes)); + } + let cif = Cif::new(cif_args, R::reify().into_middle()); + // SAFETY: Caller upholds that the function is safe to call. unsafe { cif.call(fun, &arg_ptrs) } } @@ -31,16 +34,4 @@ impl OwnedArg { pub fn new(ty: FfiType, bytes: Box<[u8]>) -> Self { Self { ty: Some(ty), bytes } } - - /// Creates a libffi argument pointer pointing to this argument's bytes. - /// NB: Since `libffi::middle::Arg` ignores the lifetime of the reference - /// it's derived from, it is up to the caller to ensure the `OwnedArg` is - /// not dropped before unsafely calling `libffi::middle::Cif::call()`! - fn ptr(&self) -> ArgPtr { - // FIXME: Using `&self.bytes[0]` to reference the whole array is - // definitely unsound under SB, but we're waiting on - // https://github.com/libffi-rs/libffi-rs/commit/112a37b3b6ffb35bd75241fbcc580de40ba74a73 - // to land in a release so that we don't need to do this. - ArgPtr::new(&self.bytes[0]) - } } From 51cab7c0dfbdfc9830cccecafac5e4a1af6ae80f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 15 Oct 2025 14:17:22 +0200 Subject: [PATCH 15/24] avoid some Vector allocations --- src/tools/miri/src/shims/native_lib/ffi.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/src/shims/native_lib/ffi.rs b/src/tools/miri/src/shims/native_lib/ffi.rs index 7448b6601959f..196f43c6f6a6c 100644 --- a/src/tools/miri/src/shims/native_lib/ffi.rs +++ b/src/tools/miri/src/shims/native_lib/ffi.rs @@ -9,13 +9,8 @@ use libffi::middle::{Arg as ArgPtr, Cif, Type as FfiType}; /// /// The safety invariants of the foreign function being called must be upheld (if any). pub unsafe fn call(fun: CodePtr, args: &mut [OwnedArg]) -> R { - let mut cif_args = vec![]; - let mut arg_ptrs = vec![]; - for a in args { - cif_args.push(a.ty.take().unwrap()); - arg_ptrs.push(ArgPtr::new(&*a.bytes)); - } - let cif = Cif::new(cif_args, R::reify().into_middle()); + let cif = Cif::new(args.iter_mut().map(|arg| arg.ty.take().unwrap()), R::reify().into_middle()); + let arg_ptrs: Vec<_> = args.iter().map(|arg| ArgPtr::new(&*arg.bytes)).collect(); // SAFETY: Caller upholds that the function is safe to call. unsafe { cif.call(fun, &arg_ptrs) } } From 1de9b49f24f65d29a7ce04dd55e4abe6a48666e9 Mon Sep 17 00:00:00 2001 From: h3nryc0ding Date: Wed, 15 Oct 2025 18:48:25 +0200 Subject: [PATCH 16/24] remove duplicate inline macro --- library/alloc/src/sync.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index 5927d03646928..c78f2c8a47e00 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -886,7 +886,6 @@ impl Arc { /// let five = Arc::try_new_in(5, System)?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` - #[inline] #[unstable(feature = "allocator_api", issue = "32838")] #[inline] pub fn try_new_in(data: T, alloc: A) -> Result, AllocError> { From 0ecee8c1b8be411cda5fabc527c78d30eaafd9f8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 15 Oct 2025 21:01:18 +0200 Subject: [PATCH 17/24] Prepare for merging from rust-lang/rust This updates the rust-version file to 28d0a4a205f9e511ad2f51ee79a4aa19a704a455. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index f4bdbbff8ea7c..1160b1b97285d 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -235a4c083eb2a2bfe8779d211c3232f39396de00 +28d0a4a205f9e511ad2f51ee79a4aa19a704a455 From 984542c4e229bbb5be070d1e276022f0b68513ec Mon Sep 17 00:00:00 2001 From: pommicket Date: Wed, 15 Oct 2025 15:43:43 -0400 Subject: [PATCH 18/24] Don't highlight `let` expressions as having type `bool` --- compiler/rustc_hir_typeck/src/demand.rs | 4 +++- tests/ui/binop/let-chain-type-issue-147665.rs | 7 +++++++ tests/ui/binop/let-chain-type-issue-147665.stderr | 9 +++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/ui/binop/let-chain-type-issue-147665.rs create mode 100644 tests/ui/binop/let-chain-type-issue-147665.stderr diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index fb6ebe066a8f2..880b0ca2fb1fe 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -792,7 +792,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(_, lhs, rhs), .. }), Some(TypeError::Sorts(ExpectedFound { expected, .. })), ) if rhs.hir_id == expr.hir_id - && self.typeck_results.borrow().expr_ty_adjusted_opt(lhs) == Some(expected) => + && self.typeck_results.borrow().expr_ty_adjusted_opt(lhs) == Some(expected) + // let expressions being marked as `bool` is confusing (see issue #147665) + && !matches!(lhs.kind, hir::ExprKind::Let(..)) => { err.span_label(lhs.span, format!("expected because this is `{expected}`")); } diff --git a/tests/ui/binop/let-chain-type-issue-147665.rs b/tests/ui/binop/let-chain-type-issue-147665.rs new file mode 100644 index 0000000000000..4a6b40c6f9877 --- /dev/null +++ b/tests/ui/binop/let-chain-type-issue-147665.rs @@ -0,0 +1,7 @@ +// Shouldn't highlight `let x = 1` as having type bool. +//@ edition:2024 + +fn main() { + if let x = 1 && 2 {} + //~^ ERROR mismatched types +} diff --git a/tests/ui/binop/let-chain-type-issue-147665.stderr b/tests/ui/binop/let-chain-type-issue-147665.stderr new file mode 100644 index 0000000000000..b2b82228eaebf --- /dev/null +++ b/tests/ui/binop/let-chain-type-issue-147665.stderr @@ -0,0 +1,9 @@ +error[E0308]: mismatched types + --> $DIR/let-chain-type-issue-147665.rs:5:21 + | +LL | if let x = 1 && 2 {} + | ^ expected `bool`, found integer + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From 952be8e88673f5ea023bbd7974c69d392e740ddb Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 15 Oct 2025 21:58:02 +0200 Subject: [PATCH 19/24] fix genmc build --- src/tools/miri/src/concurrency/genmc/global_allocations.rs | 2 +- src/tools/miri/src/concurrency/genmc/run.rs | 1 + src/tools/miri/src/concurrency/genmc/scheduling.rs | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/concurrency/genmc/global_allocations.rs b/src/tools/miri/src/concurrency/genmc/global_allocations.rs index 272f8d2484038..7f34c60dcdaff 100644 --- a/src/tools/miri/src/concurrency/genmc/global_allocations.rs +++ b/src/tools/miri/src/concurrency/genmc/global_allocations.rs @@ -6,7 +6,7 @@ use rand::SeedableRng; use rand::rngs::StdRng; use rustc_const_eval::interpret::{AllocId, AllocInfo, InterpResult, interp_ok}; use rustc_data_structures::fx::FxHashMap; -use tracing::debug; +use rustc_log::tracing::debug; use crate::alloc_addresses::AddressGenerator; diff --git a/src/tools/miri/src/concurrency/genmc/run.rs b/src/tools/miri/src/concurrency/genmc/run.rs index 6721a38c683fb..6eb51e1b50cb9 100644 --- a/src/tools/miri/src/concurrency/genmc/run.rs +++ b/src/tools/miri/src/concurrency/genmc/run.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use std::time::Instant; use genmc_sys::EstimationResult; +use rustc_log::tracing; use rustc_middle::ty::TyCtxt; use super::GlobalState; diff --git a/src/tools/miri/src/concurrency/genmc/scheduling.rs b/src/tools/miri/src/concurrency/genmc/scheduling.rs index 6ccbaf4f2482b..0703e0595b338 100644 --- a/src/tools/miri/src/concurrency/genmc/scheduling.rs +++ b/src/tools/miri/src/concurrency/genmc/scheduling.rs @@ -1,4 +1,5 @@ use genmc_sys::{ActionKind, ExecutionState}; +use rustc_data_structures::either::Either; use rustc_middle::mir::TerminatorKind; use rustc_middle::ty::{self, Ty}; @@ -38,7 +39,7 @@ fn get_next_instruction_kind<'tcx>( let Some(frame) = thread_manager.active_thread_stack().last() else { return interp_ok(NonAtomic); }; - let either::Either::Left(loc) = frame.current_loc() else { + let Either::Left(loc) = frame.current_loc() else { // We are unwinding, so the next step is definitely not atomic. return interp_ok(NonAtomic); }; From 2f044735519d326fabc2af7d199341d607081052 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 15 Oct 2025 23:30:28 +0200 Subject: [PATCH 20/24] update lockfile --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 018d03ed26c96..d135226326f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1164,7 +1164,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2106,9 +2106,9 @@ dependencies = [ [[package]] name = "libffi" -version = "4.1.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7681c6fab541f799a829e44a445a0666cf8d8a6cfebf89419e6aed52c604e87" +checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5" dependencies = [ "libc", "libffi-sys", @@ -2116,9 +2116,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "3.3.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0d828d367b4450ed08e7d510dc46636cd660055f50d67ac943bfe788767c29" +checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7" dependencies = [ "cc", ] @@ -2374,7 +2374,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] From a8c79b876bfa8bf1de4d49167391e81d02cfc60f Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Fri, 10 Oct 2025 21:29:29 -0400 Subject: [PATCH 21/24] Fix ICE on offsetted ZST pointer A grep for `const_usize.*align` found the same code copied to rustc_codegen_gcc but I don't see other cases where we get this wrong. --- .../rustc_codegen_cranelift/src/constant.rs | 11 ++++++++--- compiler/rustc_codegen_gcc/src/common.rs | 6 +++--- compiler/rustc_codegen_llvm/src/common.rs | 6 +++--- tests/ui/consts/zst_no_llvm_alloc.rs | 18 ++++++++++++++++++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 293459cc11c2f..3243e12e69999 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -5,7 +5,9 @@ use std::cmp::Ordering; use cranelift_module::*; use rustc_data_structures::fx::FxHashSet; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::mir::interpret::{AllocId, GlobalAlloc, Scalar, read_target_uint}; +use rustc_middle::mir::interpret::{ + AllocId, GlobalAlloc, PointerArithmetic, Scalar, read_target_uint, +}; use rustc_middle::ty::{ExistentialTraitRef, ScalarInt}; use crate::prelude::*; @@ -138,8 +140,11 @@ pub(crate) fn codegen_const_value<'tcx>( let base_addr = match fx.tcx.global_alloc(alloc_id) { GlobalAlloc::Memory(alloc) => { if alloc.inner().len() == 0 { - assert_eq!(offset, Size::ZERO); - fx.bcx.ins().iconst(fx.pointer_type, alloc.inner().align.bytes() as i64) + let val = alloc.inner().align.bytes().wrapping_add(offset.bytes()); + fx.bcx.ins().iconst( + fx.pointer_type, + fx.tcx.truncate_to_target_usize(val) as i64, + ) } else { let data_id = data_id_for_alloc_id( &mut fx.constants_cx, diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 28848ca61845c..7c2969e587186 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -5,7 +5,7 @@ use rustc_codegen_ssa::traits::{ BaseTypeCodegenMethods, ConstCodegenMethods, MiscCodegenMethods, StaticCodegenMethods, }; use rustc_middle::mir::Mutability; -use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, Scalar}; +use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::layout::LayoutOf; use crate::context::CodegenCx; @@ -247,8 +247,8 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { // This avoids generating a zero-sized constant value and actually needing a // real address at runtime. if alloc.inner().len() == 0 { - assert_eq!(offset.bytes(), 0); - let val = self.const_usize(alloc.inner().align.bytes()); + let val = alloc.inner().align.bytes().wrapping_add(offset.bytes()); + let val = self.const_usize(self.tcx.truncate_to_target_usize(val)); return if matches!(layout.primitive(), Pointer(_)) { self.context.new_cast(None, val, ty) } else { diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index 175fc8535ac3f..b0cf9925019d2 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -12,7 +12,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_hashes::Hash128; use rustc_hir::def_id::DefId; use rustc_middle::bug; -use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, Scalar}; +use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::TyCtxt; use rustc_session::cstore::DllImport; use tracing::debug; @@ -281,8 +281,8 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { // This avoids generating a zero-sized constant value and actually needing a // real address at runtime. if alloc.inner().len() == 0 { - assert_eq!(offset.bytes(), 0); - let llval = self.const_usize(alloc.inner().align.bytes()); + let val = alloc.inner().align.bytes().wrapping_add(offset.bytes()); + let llval = self.const_usize(self.tcx.truncate_to_target_usize(val)); return if matches!(layout.primitive(), Pointer(_)) { unsafe { llvm::LLVMConstIntToPtr(llval, llty) } } else { diff --git a/tests/ui/consts/zst_no_llvm_alloc.rs b/tests/ui/consts/zst_no_llvm_alloc.rs index 1e92e3bbd4c1b..ec5600ade16f1 100644 --- a/tests/ui/consts/zst_no_llvm_alloc.rs +++ b/tests/ui/consts/zst_no_llvm_alloc.rs @@ -1,10 +1,21 @@ //@ run-pass +// We need some non-1 alignment to test we use the alignment of the type in the compiler. #[repr(align(4))] struct Foo; static FOO: Foo = Foo; +// This tests for regression of https://github.com/rust-lang/rust/issues/147516 +// +// The compiler will codegen `&Zst` without creating a real allocation, just a properly aligned +// `usize` (i.e., ptr::dangling). However, code can add an arbitrary offset from that base +// allocation. We confirm here that we correctly codegen that offset combined with the necessary +// alignment of the base &() as a 1-ZST and &Foo as a 4-ZST. +const A: *const () = (&() as *const ()).wrapping_byte_add(2); +const B: *const () = (&Foo as *const _ as *const ()).wrapping_byte_add(usize::MAX); +const C: *const () = (&Foo as *const _ as *const ()).wrapping_byte_add(2); + fn main() { // There's no stable guarantee that these are true. // However, we want them to be true so that our LLVM IR and runtime are a bit faster: @@ -15,6 +26,13 @@ fn main() { let x: &'static Foo = &Foo; assert_eq!(x as *const Foo as usize, 4); + // * A 1-aligned ZST (1-ZST) is placed at 0x1. Then offsetting that by 2 results in 3. + // * Foo is a 4-aligned ZST, so is placed at 0x4. +2 = 6 + // * Foo is a 4-aligned ZST, so is placed at 0x4. +usize::MAX = -1 (same bit pattern) = 3 + assert_eq!(A.addr(), 3); + assert_eq!(B.addr(), 3); + assert_eq!(C.addr(), 6); + // The exact addresses returned by these library functions are not necessarily stable guarantees // but for now we assert that we're still matching. #[allow(dangling_pointers_from_temporaries)] From 8787c0b8639cdbebdd160a2ec7100c74456e07bf Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 16 Oct 2025 11:45:07 +1100 Subject: [PATCH 22/24] Use `bit_set::Word` in a couple more places. It's a synonym for `u64` and there are a couple of places where we use `u64` where we should use `Word`, which this commit fixes. I found this when I tried changing `Word` to `u128` (which made performance worse). --- compiler/rustc_index/src/bit_set.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index 0e7394006dd2d..6fa3670f8e661 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -873,7 +873,7 @@ impl BitRelations> for ChunkedBitSet { let mut self_chunk_words = **other_chunk_words; for word in self_chunk_words[0..num_words].iter_mut().rev() { *word = !*word & tail_mask; - tail_mask = u64::MAX; + tail_mask = Word::MAX; } let self_chunk_count = chunk_domain_size - *other_chunk_count; debug_assert_eq!( @@ -888,7 +888,7 @@ impl BitRelations> for ChunkedBitSet { ) => { // See `ChunkedBitSet::union` for details on what is happening here. let num_words = num_words(chunk_domain_size as usize); - let op = |a: u64, b: u64| a & !b; + let op = |a: Word, b: Word| a & !b; if !bitwise_changes( &self_chunk_words[0..num_words], &other_chunk_words[0..num_words], From 74ac3ec91a961a6c647bc77d8f768e981ad78779 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Thu, 16 Oct 2025 01:03:48 +0000 Subject: [PATCH 23/24] style-guide: fix typo for empty struct advice the advice appears to apply to empty structs with braces (parens/blocks), and a unit struct in the comment does not make sense. Fix the typo. --- src/doc/style-guide/src/items.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/style-guide/src/items.md b/src/doc/style-guide/src/items.md index be361eee330ac..090dc00844802 100644 --- a/src/doc/style-guide/src/items.md +++ b/src/doc/style-guide/src/items.md @@ -123,7 +123,7 @@ struct Foo { Prefer using a unit struct (e.g., `struct Foo;`) to an empty struct (e.g., `struct Foo();` or `struct Foo {}`, these only exist to simplify code generation), but if you must use an empty struct, keep it on one line with no -space between the braces: `struct Foo;` or `struct Foo {}`. +space between the braces: `struct Foo();` or `struct Foo {}`. The same guidelines are used for untagged union declarations. From 7a11c72db032257b709707f08d008e20dae7e8e9 Mon Sep 17 00:00:00 2001 From: timvisee Date: Thu, 16 Oct 2025 10:52:51 +0200 Subject: [PATCH 24/24] is_ascii on an empty string or slice returns true --- library/core/src/slice/ascii.rs | 2 ++ library/core/src/str/mod.rs | 2 ++ library/std/src/ffi/os_str.rs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/library/core/src/slice/ascii.rs b/library/core/src/slice/ascii.rs index e17a2e03d2dc4..42916558b5fc4 100644 --- a/library/core/src/slice/ascii.rs +++ b/library/core/src/slice/ascii.rs @@ -9,6 +9,8 @@ use crate::{ascii, iter, ops}; impl [u8] { /// Checks if all bytes in this slice are within the ASCII range. + /// + /// An empty slice returns `true`. #[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")] #[rustc_const_stable(feature = "const_slice_is_ascii", since = "1.74.0")] #[must_use] diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 3a5efa7d83511..82019b9b3afe5 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -2704,6 +2704,8 @@ impl str { /// Checks if all characters in this string are within the ASCII range. /// + /// An empty string returns `true`. + /// /// # Examples /// /// ``` diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index 6c098034eea3b..09bd911aa769a 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -1215,6 +1215,8 @@ impl OsStr { /// Checks if all characters in this string are within the ASCII range. /// + /// An empty string returns `true`. + /// /// # Examples /// /// ```