From b809a2180236f0b9d0470118e86c20e43a777591 Mon Sep 17 00:00:00 2001 From: Lewis McClelland Date: Fri, 22 Aug 2025 19:49:24 -0400 Subject: [PATCH 01/23] wip: port some old std work --- library/Cargo.lock | 8 + library/Cargo.toml | 2 + library/std/Cargo.toml | 5 + library/std/build.rs | 1 + library/std/src/env.rs | 2 + library/std/src/sys/env_consts.rs | 11 + library/std/src/sys/pal/mod.rs | 4 + library/std/src/sys/pal/vexos/fs.rs | 589 ++++++++++++++++++++++++ library/std/src/sys/pal/vexos/mod.rs | 123 +++++ library/std/src/sys/pal/vexos/stdio.rs | 108 +++++ library/std/src/sys/pal/vexos/thread.rs | 44 ++ library/std/src/sys/pal/vexos/time.rs | 46 ++ library/std/src/sys/random/mod.rs | 2 + library/std/src/sys/thread_local/mod.rs | 1 + src/tools/tidy/src/deps.rs | 1 + 15 files changed, 947 insertions(+) create mode 100644 library/std/src/sys/pal/vexos/fs.rs create mode 100644 library/std/src/sys/pal/vexos/mod.rs create mode 100644 library/std/src/sys/pal/vexos/stdio.rs create mode 100644 library/std/src/sys/pal/vexos/thread.rs create mode 100644 library/std/src/sys/pal/vexos/time.rs diff --git a/library/Cargo.lock b/library/Cargo.lock index c16bb3eafbdac..e984d30fc9744 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -329,6 +329,7 @@ dependencies = [ "unwind", "wasi 0.11.1+wasi-snapshot-preview1", "wasi 0.14.3+wasi-0.2.4", + "vex-sdk", "windows-targets 0.0.0", ] @@ -390,6 +391,13 @@ dependencies = [ "rustc-std-workspace-core", ] +[[package]] +name = "vex-sdk" +version = "0.27.0" +dependencies = [ + "rustc-std-workspace-core", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/library/Cargo.toml b/library/Cargo.toml index e30e624094285..5a354e43a352e 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -59,3 +59,5 @@ rustflags = ["-Cpanic=abort"] rustc-std-workspace-core = { path = 'rustc-std-workspace-core' } rustc-std-workspace-alloc = { path = 'rustc-std-workspace-alloc' } rustc-std-workspace-std = { path = 'rustc-std-workspace-std' } + +vex-sdk = { path = "../../vex-sdk/packages/vex-sdk" } diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 8b78fe53a3978..8aec6af8b0e83 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -89,6 +89,11 @@ wasip2 = { version = '0.14.3', features = [ r-efi = { version = "5.2.0", features = ['rustc-dep-of-std'] } r-efi-alloc = { version = "2.0.0", features = ['rustc-dep-of-std'] } +[target.'cfg(target_os = "vexos")'.dependencies] +vex-sdk = { version = "0.27.0", features = [ + 'rustc-dep-of-std', +], default-features = false } + [features] backtrace = [ 'addr2line/rustc-dep-of-std', diff --git a/library/std/build.rs b/library/std/build.rs index ef695601a448a..8a5a785060c85 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -52,6 +52,7 @@ fn main() { || target_os == "rtems" || target_os == "nuttx" || target_os == "cygwin" + || target_os == "vexos" // See src/bootstrap/src/core/build_steps/synthetic_targets.rs || env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok() diff --git a/library/std/src/env.rs b/library/std/src/env.rs index e457cd61c7596..6d716bd854433 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -1098,6 +1098,7 @@ pub mod consts { /// * `"redox"` /// * `"solaris"` /// * `"solid_asp3` + /// * `"vexos"` /// * `"vita"` /// * `"vxworks"` /// * `"xous"` @@ -1148,6 +1149,7 @@ pub mod consts { /// ///
Full list of possible values /// + /// * `"bin"` /// * `"exe"` /// * `"efi"` /// * `"js"` diff --git a/library/std/src/sys/env_consts.rs b/library/std/src/sys/env_consts.rs index 9683fd47cf96b..91c00888a706f 100644 --- a/library/std/src/sys/env_consts.rs +++ b/library/std/src/sys/env_consts.rs @@ -323,6 +323,17 @@ pub mod os { pub const EXE_EXTENSION: &str = "efi"; } +#[cfg(target_os = "vexos")] +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = "vexos"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ""; + pub const DLL_EXTENSION: &str = ""; + pub const EXE_SUFFIX: &str = ".bin"; + pub const EXE_EXTENSION: &str = "bin"; +} + #[cfg(target_os = "visionos")] pub mod os { pub const FAMILY: &str = "unix"; diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs index 513121c6d30ee..dd5e83ee570b6 100644 --- a/library/std/src/sys/pal/mod.rs +++ b/library/std/src/sys/pal/mod.rs @@ -45,6 +45,10 @@ cfg_select! { mod trusty; pub use self::trusty::*; } + target_os = "vexos" => { + mod vexos; + pub use self::vexos::*; + } all(target_os = "wasi", target_env = "p2") => { mod wasip2; pub use self::wasip2::*; diff --git a/library/std/src/sys/pal/vexos/fs.rs b/library/std/src/sys/pal/vexos/fs.rs new file mode 100644 index 0000000000000..1014bab30bb86 --- /dev/null +++ b/library/std/src/sys/pal/vexos/fs.rs @@ -0,0 +1,589 @@ +use crate::ffi::{CString, OsString}; +use crate::fmt; +use crate::hash::Hash; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::path::{Path, PathBuf}; +use crate::sys::time::SystemTime; +use crate::sys::unsupported; + +#[derive(Debug)] +struct FileDesc(*mut vex_sdk::FIL); + +pub struct File { + fd: FileDesc, +} + +#[derive(Clone)] +pub struct FileAttr { + size: u64, + is_dir: bool, +} + +#[derive(Debug)] +pub struct ReadDir { + entries: Vec, +} + +#[derive(Debug)] +pub struct DirEntry { + path: PathBuf, +} + +#[derive(Clone, Debug)] +pub struct OpenOptions { + read: bool, + write: bool, + append: bool, + truncate: bool, + create_new: bool, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct FileTimes(()); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FilePermissions; + +#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)] +pub struct FileType { + is_dir: bool, +} + +#[derive(Debug)] +pub struct DirBuilder(()); + +impl FileAttr { + /// Creates a FileAttr by getting data from an opened file. + fn from_fd(fd: *mut vex_sdk::FIL) -> io::Result { + let size = unsafe { vex_sdk::vexFileSize(fd) }; + + if size >= 0 { + Ok(Self { size: size as u64, is_dir: false }) + } else { + Err(io::Error::new(io::ErrorKind::InvalidData, "Failed to get file size")) + } + } + + fn from_path(path: &Path) -> io::Result { + let c_path = CString::new(path.as_os_str().as_encoded_bytes()).map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "Path contained a null byte") + })?; + + let file_type = unsafe { vex_sdk::vexFileStatus(c_path.as_ptr()) }; + let is_dir = file_type == 3; + + // We can't get the size if its a directory because we cant open it as a file + if is_dir { + Ok(Self { size: 0, is_dir: true }) + } else { + let mut opts = OpenOptions::new(); + opts.read(true); + let file = File::open(path, &opts)?; + let fd = file.fd.0; + + Self::from_fd(fd) + } + } + + pub fn size(&self) -> u64 { + self.size + } + + pub fn perm(&self) -> FilePermissions { + FilePermissions + } + + pub fn file_type(&self) -> FileType { + FileType { is_dir: self.is_dir } + } + + pub fn modified(&self) -> io::Result { + unsupported() + } + + pub fn accessed(&self) -> io::Result { + unsupported() + } + + pub fn created(&self) -> io::Result { + unsupported() + } +} + +impl FilePermissions { + pub fn readonly(&self) -> bool { + false + } + + pub fn set_readonly(&mut self, _readonly: bool) { + panic!("Perimissions do not exist") + } +} + +impl FileTimes { + pub fn set_accessed(&mut self, _t: SystemTime) {} + pub fn set_modified(&mut self, _t: SystemTime) {} +} + +impl FileType { + pub fn is_dir(&self) -> bool { + self.is_dir + } + + pub fn is_file(&self) -> bool { + !self.is_dir + } + + pub fn is_symlink(&self) -> bool { + false + } +} + +impl Iterator for ReadDir { + type Item = io::Result; + + fn next(&mut self) -> Option> { + self.entries.pop().map(Ok) + } +} + +impl DirEntry { + pub fn path(&self) -> PathBuf { + self.path.clone() + } + + pub fn file_name(&self) -> OsString { + self.path.file_name().unwrap_or(crate::ffi::OsStr::new("")).to_os_string() + } + + pub fn metadata(&self) -> io::Result { + stat(&self.path) + } + + pub fn file_type(&self) -> io::Result { + Ok(self.metadata()?.file_type()) + } +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + OpenOptions { read: false, write: false, append: false, truncate: false, create_new: false } + } + + pub fn read(&mut self, read: bool) { + self.read = read; + } + pub fn write(&mut self, write: bool) { + self.write = write; + } + pub fn append(&mut self, append: bool) { + self.append = append; + } + pub fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + pub fn create(&mut self, create: bool) { + self.write = create; + } + pub fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; + } +} + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + // Mount sdcard volume as FAT filesystem + map_fresult(unsafe { vex_sdk::vexFileMountSD() })?; + + let path = CString::new(path.as_os_str().as_encoded_bytes()).map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "Path contained a null byte") + })?; + + if opts.write && opts.read { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Files cannot be opened with read and write access", + )); + } + if opts.create_new { + let file_exists = unsafe { vex_sdk::vexFileStatus(path.as_ptr()) }; + if file_exists != 0 { + return Err(io::Error::new(io::ErrorKind::AlreadyExists, "File already exists")); + } + } + + let file = if opts.read && !opts.write { + // The second argument to this function is ignored. + // Open in read only mode + unsafe { vex_sdk::vexFileOpen(path.as_ptr(), c"".as_ptr()) } + } else if opts.write && opts.append { + // Open in write and append mode + unsafe { vex_sdk::vexFileOpenWrite(path.as_ptr()) } + } else if opts.write && opts.truncate { + // Open in write mode + unsafe { vex_sdk::vexFileOpenCreate(path.as_ptr()) } + } else if opts.write { + unsafe { + // Open in read/write and append mode + let fd = vex_sdk::vexFileOpenWrite(path.as_ptr()); + // Seek to beginning of the file + vex_sdk::vexFileSeek(fd, 0, 0); + + fd + } + } else { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Files cannot be opened without read or write access", + )); + }; + + if file.is_null() { + Err(io::Error::new(io::ErrorKind::NotFound, "Could not open file")) + } else { + Ok(Self { fd: FileDesc(file) }) + } + } + + pub fn file_attr(&self) -> io::Result { + FileAttr::from_fd(self.fd.0) + } + + pub fn fsync(&self) -> io::Result<()> { + self.flush() + } + + pub fn datasync(&self) -> io::Result<()> { + self.flush() + } + + pub fn lock(&self) -> io::Result<()> { + unsupported() + } + + pub fn lock_shared(&self) -> io::Result<()> { + unsupported() + } + + pub fn try_lock(&self) -> io::Result { + unsupported() + } + + pub fn try_lock_shared(&self) -> io::Result { + unsupported() + } + + pub fn unlock(&self) -> io::Result<()> { + unsupported() + } + + pub fn truncate(&self, _size: u64) -> io::Result<()> { + unsupported() + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + let len = buf.len() as _; + let buf_ptr = buf.as_mut_ptr(); + let read = unsafe { vex_sdk::vexFileRead(buf_ptr.cast(), 1, len, self.fd.0) }; + if read < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Could not read from file")) + } else { + Ok(read as usize) + } + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + crate::io::default_read_vectored(|b| self.read(b), bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|b| self.read(b), cursor) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + let len = buf.len(); + let buf_ptr = buf.as_ptr(); + let written = + unsafe { vex_sdk::vexFileWrite(buf_ptr.cast_mut().cast(), 1, len as _, self.fd.0) }; + if written < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Could not write to file")) + } else { + Ok(written as usize) + } + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + crate::io::default_write_vectored(|b| self.write(b), bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn flush(&self) -> io::Result<()> { + unsafe { + vex_sdk::vexFileSync(self.fd.0); + } + Ok(()) + } + + fn tell(&self) -> io::Result { + let position = unsafe { vex_sdk::vexFileTell(self.fd.0) }; + position.try_into().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "Failed to get current location in file") + }) + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result { + const SEEK_SET: i32 = 0; + const SEEK_CUR: i32 = 1; + const SEEK_END: i32 = 2; + + fn try_convert_offset>(offset: T) -> io::Result { + offset.try_into().map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidInput, + "Cannot seek to an offset too large to fit in a 32 bit integer", + ) + }) + } + + match pos { + SeekFrom::Start(offset) => unsafe { + map_fresult(vex_sdk::vexFileSeek(self.fd.0, try_convert_offset(offset)?, SEEK_SET))? + }, + SeekFrom::End(offset) => unsafe { + if offset >= 0 { + map_fresult(vex_sdk::vexFileSeek( + self.fd.0, + try_convert_offset(offset)?, + SEEK_END, + ))? + } else { + // `vexFileSeek` does not support seeking with negative offset, meaning + // we have to calculate the offset from the end of the file ourselves. + map_fresult(vex_sdk::vexFileSeek( + self.fd.0, + try_convert_offset(self.file_attr()?.size as i64 + offset)?, + SEEK_SET, + ))? + } + }, + SeekFrom::Current(offset) => unsafe { + if offset >= 0 { + map_fresult(vex_sdk::vexFileSeek( + self.fd.0, + try_convert_offset(offset)?, + SEEK_CUR, + ))? + } else { + // `vexFileSeek` does not support seeking with negative offset, meaning + // we have to calculate the offset from the stream position ourselves. + map_fresult(vex_sdk::vexFileSeek( + self.fd.0, + try_convert_offset((self.tell()? as i64) + offset)?, + SEEK_SET, + ))? + } + }, + } + + Ok(self.tell()?) + } + + pub fn duplicate(&self) -> io::Result { + unsupported() + } + + pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { + unsupported() + } + + pub fn set_times(&self, _times: FileTimes) -> io::Result<()> { + unsupported() + } +} + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder(()) + } + + pub fn mkdir(&self, _p: &Path) -> io::Result<()> { + unsupported() + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("File").field("fd", &self.fd.0).finish() + } +} +impl Drop for File { + fn drop(&mut self) { + unsafe { vex_sdk::vexFileClose(self.fd.0) }; + } +} + +pub fn readdir(_p: &Path) -> io::Result { + // While there *is* a userspace function for reading file directories, + // the necessary implementation cannot currently be done cleanly, as + // VEXos does not expose directory length to user programs. + // + // This means that we would need to create a large fixed-length buffer + // and hope that the folder's contents didn't exceed that buffer's length, + // which obviously isn't behavior we want to rely on in the standard library. + unsupported() +} + +pub fn unlink(_p: &Path) -> io::Result<()> { + unsupported() +} + +pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + +pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + unsupported() +} + +pub fn rmdir(_p: &Path) -> io::Result<()> { + unsupported() +} + +pub fn remove_dir_all(_path: &Path) -> io::Result<()> { + unsupported() +} + +pub fn exists(path: &Path) -> io::Result { + let path = CString::new(path.as_os_str().as_encoded_bytes()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Path contained a null byte"))?; + + let file_exists = unsafe { vex_sdk::vexFileStatus(path.as_ptr()) }; + if file_exists != 0 { Ok(true) } else { Ok(false) } +} + +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(p: &Path) -> io::Result { + FileAttr::from_path(p) +} + +pub fn lstat(p: &Path) -> io::Result { + // Symlinks aren't supported in this filesystem + stat(p) +} + +pub fn canonicalize(_p: &Path) -> io::Result { + unsupported() +} + +pub fn copy(from: &Path, to: &Path) -> io::Result { + use crate::fs::File; + + let mut reader = File::open(from)?; + let mut writer = File::create(to)?; + + io::copy(&mut reader, &mut writer) +} + +fn map_fresult(fresult: vex_sdk::FRESULT) -> io::Result<()> { + // VEX presumably uses a derivative of FatFs (most likely the xilffs library) + // for sdcard filesystem functions. + // + // Documentation for each FRESULT originates from here: + // + match fresult { + vex_sdk::FRESULT::FR_OK => Ok(()), + vex_sdk::FRESULT::FR_DISK_ERR => Err(io::Error::new( + io::ErrorKind::Uncategorized, + "internal function reported an unrecoverable hard error", + )), + vex_sdk::FRESULT::FR_INT_ERR => Err(io::Error::new( + io::ErrorKind::Uncategorized, + "internal error in filesystem runtime", + )), + vex_sdk::FRESULT::FR_NOT_READY => Err(io::Error::new( + io::ErrorKind::Uncategorized, + "the storage device could not be prepared to work", + )), + vex_sdk::FRESULT::FR_NO_FILE => { + Err(io::Error::new(io::ErrorKind::NotFound, "could not find the file in the directory")) + } + vex_sdk::FRESULT::FR_NO_PATH => Err(io::Error::new( + io::ErrorKind::NotFound, + "a directory in the path name could not be found", + )), + vex_sdk::FRESULT::FR_INVALID_NAME => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "the given string is invalid as a path name", + )), + vex_sdk::FRESULT::FR_DENIED => Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "the required access for this operation was denied", + )), + vex_sdk::FRESULT::FR_EXIST => Err(io::Error::new( + io::ErrorKind::AlreadyExists, + "an object with the same name already exists in the directory", + )), + vex_sdk::FRESULT::FR_INVALID_OBJECT => Err(io::Error::new( + io::ErrorKind::Uncategorized, + "invalid or null file/directory object", + )), + vex_sdk::FRESULT::FR_WRITE_PROTECTED => Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "a write operation was performed on write-protected media", + )), + vex_sdk::FRESULT::FR_INVALID_DRIVE => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "an invalid drive number was specified in the path name", + )), + vex_sdk::FRESULT::FR_NOT_ENABLED => Err(io::Error::new( + io::ErrorKind::Uncategorized, + "work area for the logical drive has not been registered", + )), + vex_sdk::FRESULT::FR_NO_FILESYSTEM => Err(io::Error::new( + io::ErrorKind::Uncategorized, + "valid FAT volume could not be found on the drive", + )), + vex_sdk::FRESULT::FR_MKFS_ABORTED => { + Err(io::Error::new(io::ErrorKind::Uncategorized, "failed to create filesystem volume")) + } + vex_sdk::FRESULT::FR_TIMEOUT => Err(io::Error::new( + io::ErrorKind::TimedOut, + "the function was canceled due to a timeout of thread-safe control", + )), + vex_sdk::FRESULT::FR_LOCKED => Err(io::Error::new( + io::ErrorKind::Uncategorized, + "the operation to the object was rejected by file sharing control", + )), + vex_sdk::FRESULT::FR_NOT_ENOUGH_CORE => { + Err(io::Error::new(io::ErrorKind::OutOfMemory, "not enough memory for the operation")) + } + vex_sdk::FRESULT::FR_TOO_MANY_OPEN_FILES => Err(io::Error::new( + io::ErrorKind::Uncategorized, + "maximum number of open files has been reached", + )), + vex_sdk::FRESULT::FR_INVALID_PARAMETER => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "a given parameter was invalid")) + } + _ => unreachable!(), // C-style enum + } +} diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs new file mode 100644 index 0000000000000..282443046b106 --- /dev/null +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -0,0 +1,123 @@ +#[path = "../unsupported/args.rs"] +pub mod args; +pub mod env; +pub mod fs; +#[path = "../unsupported/io.rs"] +pub mod io; +#[path = "../unsupported/net.rs"] +pub mod net; +#[path = "../unsupported/os.rs"] +pub mod os; +#[path = "../unsupported/pipe.rs"] +pub mod pipe; +#[path = "../unsupported/process.rs"] +pub mod process; +pub mod stdio; +pub mod thread; +pub mod time; + +use crate::arch::global_asm; +use crate::ptr::{self, addr_of_mut}; +use crate::time::{Duration, Instant}; + +global_asm!( + r#" + .section .boot, "ax" + .global _boot + + _boot: + ldr sp, =__stack_top @ Set up the user stack. + b _start @ Jump to the Rust entrypoint. + "# +); + +#[cfg(not(test))] +#[no_mangle] +pub unsafe extern "C" fn _start() -> ! { + extern "C" { + static mut __bss_start: u8; + static mut __bss_end: u8; + + fn main() -> i32; + } + + // VEXos doesn't explicitly clean out .bss. + ptr::slice_from_raw_parts_mut( + addr_of_mut!(__bss_start), + addr_of_mut!(__bss_end).offset_from(addr_of_mut!(__bss_start)) as usize, + ) + .as_mut() + .unwrap_unchecked() + .fill(0); + + main(); + + abort_internal() +} + +// The code signature is a 32 byte header at the start of user programs that +// identifies the owner and type of the program, as well as certain flags for +// program behavior dictated by the OS. In the event that the user wants to +// change this header, we use weak linkage so it can be overwritten. +#[link_section = ".code_signature"] +#[linkage = "weak"] +#[used] +static CODE_SIGNATURE: vex_sdk::vcodesig = + vex_sdk::vcodesig { magic: u32::from_le_bytes(*b"XVX5"), r#type: 0, owner: 2, options: 0 }; + +// This function is needed by the panic runtime. The symbol is named in +// pre-link args for the target specification, so keep that in sync. +#[cfg(not(test))] +#[no_mangle] +// NB. used by both libunwind and libpanic_abort +pub extern "C" fn __rust_abort() -> ! { + abort_internal() +} + +// SAFETY: must be called only once during runtime initialization. +// NOTE: this is not guaranteed to run, for example when Rust code is called externally. +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() -> crate::io::Result { + Err(unsupported_err()) +} + +pub fn unsupported_err() -> crate::io::Error { + crate::io::Error::UNSUPPORTED_PLATFORM +} + +pub fn is_interrupted(_code: i32) -> bool { + false +} + +pub fn decode_error_kind(_code: i32) -> crate::io::ErrorKind { + crate::io::ErrorKind::Uncategorized +} + +pub fn abort_internal() -> ! { + let exit_time = Instant::now(); + const FLUSH_TIMEOUT: Duration = Duration::from_millis(15); + + unsafe { + // Force the serial buffer to flush + while exit_time.elapsed() < FLUSH_TIMEOUT { + vex_sdk::vexTasksRun(); + + // If the buffer has been fully flushed, exit the loop + if vex_sdk::vexSerialWriteFree(stdio::STDIO_CHANNEL) == (stdio::STDOUT_BUF_SIZE as i32) + { + break; + } + } + + vex_sdk::vexSystemExitRequest(); + + loop { + vex_sdk::vexTasksRun(); + } + } +} diff --git a/library/std/src/sys/pal/vexos/stdio.rs b/library/std/src/sys/pal/vexos/stdio.rs new file mode 100644 index 0000000000000..94c834843806f --- /dev/null +++ b/library/std/src/sys/pal/vexos/stdio.rs @@ -0,0 +1,108 @@ +use crate::io; + +pub struct Stdin(()); +pub struct Stdout(()); +pub struct Stderr(()); + +pub const STDIO_CHANNEL: u32 = 1; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin(()) + } +} + +impl io::Read for Stdin { + fn read(&mut self, mut buf: &mut [u8]) -> io::Result { + let mut written = 0; + + while let Some((out_byte, new_buf)) = buf.split_first_mut() { + buf = new_buf; + + let byte = unsafe { vex_sdk::vexSerialReadChar(STDIO_CHANNEL) }; + if byte < 0 { + break; + } + + *out_byte = byte as u8; + written += 1; + } + + Ok(written) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout(()) + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + let written = + unsafe { vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, buf.as_ptr(), buf.len() as u32) }; + + if written < 0 { + return Err(io::Error::new( + io::ErrorKind::Uncategorized, + "Internal write error occurred.", + )); + } + + Ok(written as usize) + } + + fn flush(&mut self) -> io::Result<()> { + unsafe { + while (vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) as usize) != STDOUT_BUF_SIZE { + vex_sdk::vexTasksRun(); + } + } + + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr(()) + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + let written = + unsafe { vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, buf.as_ptr(), buf.len() as u32) }; + + if written < 0 { + return Err(io::Error::new( + io::ErrorKind::Uncategorized, + "Internal write error occurred.", + )); + } + + Ok(written as usize) + } + + fn flush(&mut self) -> io::Result<()> { + unsafe { + while (vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) as usize) != STDOUT_BUF_SIZE { + vex_sdk::vexTasksRun(); + } + } + + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = 4096; +pub const STDOUT_BUF_SIZE: usize = 2048; + +pub fn is_ebadf(_err: &io::Error) -> bool { + false +} + +pub fn panic_output() -> Option { + Some(Stdout::new()) +} diff --git a/library/std/src/sys/pal/vexos/thread.rs b/library/std/src/sys/pal/vexos/thread.rs new file mode 100644 index 0000000000000..f00ff9f7b1066 --- /dev/null +++ b/library/std/src/sys/pal/vexos/thread.rs @@ -0,0 +1,44 @@ +use super::unsupported; +use crate::ffi::CStr; +use crate::io; +use crate::num::NonZero; +use crate::time::{Duration, Instant}; + +pub struct Thread(!); + +pub const DEFAULT_MIN_STACK_SIZE: usize = 64 * 1024; + +impl Thread { + // unsafe: see thread::Builder::spawn_unchecked for safety requirements + pub unsafe fn new(_stack: usize, _p: Box) -> io::Result { + unsupported() + } + + pub fn yield_now() { + unsafe { + vex_sdk::vexTasksRun(); + } + } + + pub fn set_name(_name: &CStr) { + // nope + } + + pub fn sleep(dur: Duration) { + let start = Instant::now(); + + while start.elapsed() < dur { + unsafe { + vex_sdk::vexTasksRun(); + } + } + } + + pub fn join(self) { + self.0 + } +} + +pub fn available_parallelism() -> io::Result> { + unsupported() +} diff --git a/library/std/src/sys/pal/vexos/time.rs b/library/std/src/sys/pal/vexos/time.rs new file mode 100644 index 0000000000000..60805ea2759d0 --- /dev/null +++ b/library/std/src/sys/pal/vexos/time.rs @@ -0,0 +1,46 @@ +use crate::time::Duration; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Instant(Duration); + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SystemTime(Duration); + +pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); + +impl Instant { + pub fn now() -> Instant { + let micros = unsafe { vex_sdk::vexSystemHighResTimeGet() }; + Self(Duration::from_micros(micros)) + } + + pub fn checked_sub_instant(&self, other: &Instant) -> Option { + self.0.checked_sub(other.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + Some(Instant(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + Some(Instant(self.0.checked_sub(*other)?)) + } +} + +impl SystemTime { + pub fn now() -> SystemTime { + panic!("system time not implemented on this platform") + } + + pub fn sub_time(&self, other: &SystemTime) -> Result { + self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + Some(SystemTime(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + Some(SystemTime(self.0.checked_sub(*other)?)) + } +} diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs index 1e0eec07b5006..3c5a4c82a9f1e 100644 --- a/library/std/src/sys/random/mod.rs +++ b/library/std/src/sys/random/mod.rs @@ -101,6 +101,7 @@ cfg_select! { any( all(target_family = "wasm", target_os = "unknown"), target_os = "xous", + target_os = "vexos", ) => { // FIXME: finally remove std support for wasm32-unknown-unknown // FIXME: add random data generation to xous @@ -116,6 +117,7 @@ cfg_select! { all(target_family = "wasm", target_os = "unknown"), all(target_os = "wasi", target_env = "p2"), target_os = "xous", + target_os = "vexos", )))] pub fn hashmap_random_keys() -> (u64, u64) { let mut buf = [0; 16]; diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index cff74857c4733..944f2dc6ae38a 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -29,6 +29,7 @@ cfg_select! { target_os = "uefi", target_os = "zkvm", target_os = "trusty", + target_os = "vexos", ) => { mod no_threads; pub use no_threads::{EagerStorage, LazyStorage, thread_local_inner}; diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 560f11ecf50ec..53ed40d68ddcb 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -479,6 +479,7 @@ const PERMITTED_STDLIB_DEPENDENCIES: &[&str] = &[ "shlex", "unicode-width", "unwinding", + "vex-sdk", "wasi", "windows-sys", "windows-targets", From 3317cd7cf432220434e723dc97c83c20042f0fcd Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:57:14 -0500 Subject: [PATCH 02/23] vexos: move fs and stdio into respective modules --- .../src/spec/targets/armv7a_vex_v5.rs | 2 +- library/Cargo.lock | 1 + library/Cargo.toml | 2 +- library/std/Cargo.toml | 2 +- library/std/src/sys/fs/mod.rs | 4 ++ .../src/sys/{pal/vexos/fs.rs => fs/vexos.rs} | 6 +-- library/std/src/sys/pal/vexos/mod.rs | 45 +++---------------- library/std/src/sys/stdio/mod.rs | 4 ++ .../{pal/vexos/stdio.rs => stdio/vexos.rs} | 44 +++--------------- 9 files changed, 26 insertions(+), 84 deletions(-) rename library/std/src/sys/{pal/vexos/fs.rs => fs/vexos.rs} (98%) rename library/std/src/sys/{pal/vexos/stdio.rs => stdio/vexos.rs} (62%) diff --git a/compiler/rustc_target/src/spec/targets/armv7a_vex_v5.rs b/compiler/rustc_target/src/spec/targets/armv7a_vex_v5.rs index e78f783997470..06dd26297758f 100644 --- a/compiler/rustc_target/src/spec/targets/armv7a_vex_v5.rs +++ b/compiler/rustc_target/src/spec/targets/armv7a_vex_v5.rs @@ -34,7 +34,7 @@ pub(crate) fn target() -> Target { description: Some("ARMv7-A Cortex-A9 VEX V5 Brain".into()), tier: Some(3), host_tools: Some(false), - std: Some(false), + std: Some(true), }, pointer_width: 32, data_layout: "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".into(), diff --git a/library/Cargo.lock b/library/Cargo.lock index e984d30fc9744..6ad330d920ce6 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -394,6 +394,7 @@ dependencies = [ [[package]] name = "vex-sdk" version = "0.27.0" +source = "git+https://github.com/vexide/vex-sdk.git#67771319f730557693b2aa1e84c7ecd4831efb13" dependencies = [ "rustc-std-workspace-core", ] diff --git a/library/Cargo.toml b/library/Cargo.toml index 5a354e43a352e..74a6ef26baeb8 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -60,4 +60,4 @@ rustc-std-workspace-core = { path = 'rustc-std-workspace-core' } rustc-std-workspace-alloc = { path = 'rustc-std-workspace-alloc' } rustc-std-workspace-std = { path = 'rustc-std-workspace-std' } -vex-sdk = { path = "../../vex-sdk/packages/vex-sdk" } +vex-sdk = { git = "https://github.com/vexide/vex-sdk.git" } diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 8aec6af8b0e83..d6db90bdedab6 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -90,7 +90,7 @@ r-efi = { version = "5.2.0", features = ['rustc-dep-of-std'] } r-efi-alloc = { version = "2.0.0", features = ['rustc-dep-of-std'] } [target.'cfg(target_os = "vexos")'.dependencies] -vex-sdk = { version = "0.27.0", features = [ +vex-sdk = { git = "https://github.com/vexide/vex-sdk.git", features = [ 'rustc-dep-of-std', ], default-features = false } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 0276bf6e64c8b..64f5a6b36d3db 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -35,6 +35,10 @@ cfg_select! { mod uefi; use uefi as imp; } + target_os = "vexos" => { + mod vexos; + use vexos as imp; + } target_os = "wasi" => { mod wasi; use wasi as imp; diff --git a/library/std/src/sys/pal/vexos/fs.rs b/library/std/src/sys/fs/vexos.rs similarity index 98% rename from library/std/src/sys/pal/vexos/fs.rs rename to library/std/src/sys/fs/vexos.rs index 1014bab30bb86..914c03dbcf6db 100644 --- a/library/std/src/sys/pal/vexos/fs.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -505,11 +505,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { } fn map_fresult(fresult: vex_sdk::FRESULT) -> io::Result<()> { - // VEX presumably uses a derivative of FatFs (most likely the xilffs library) - // for sdcard filesystem functions. - // - // Documentation for each FRESULT originates from here: - // + // VEX uses a derivative of FatFs (Xilinx's xilffs library) for filesystem operations. match fresult { vex_sdk::FRESULT::FR_OK => Ok(()), vex_sdk::FRESULT::FR_DISK_ERR => Err(io::Error::new( diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs index 282443046b106..c8d4a28c4aa7b 100644 --- a/library/std/src/sys/pal/vexos/mod.rs +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -1,18 +1,7 @@ -#[path = "../unsupported/args.rs"] -pub mod args; -pub mod env; -pub mod fs; -#[path = "../unsupported/io.rs"] -pub mod io; -#[path = "../unsupported/net.rs"] -pub mod net; #[path = "../unsupported/os.rs"] pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; -pub mod stdio; pub mod thread; pub mod time; @@ -41,39 +30,19 @@ pub unsafe extern "C" fn _start() -> ! { fn main() -> i32; } - // VEXos doesn't explicitly clean out .bss. - ptr::slice_from_raw_parts_mut( - addr_of_mut!(__bss_start), - addr_of_mut!(__bss_end).offset_from(addr_of_mut!(__bss_start)) as usize, - ) - .as_mut() - .unwrap_unchecked() - .fill(0); + // Clear the .bss (uninitialized statics) section by filling it with zeroes. + // This is required, since the compiler assumes it will be zeroed on first access. + ptr::write_bytes( + &raw mut __bss_start, + 0, + (&raw mut __bss_end).offset_from(&raw mut __bss_start) as usize, + ); main(); abort_internal() } -// The code signature is a 32 byte header at the start of user programs that -// identifies the owner and type of the program, as well as certain flags for -// program behavior dictated by the OS. In the event that the user wants to -// change this header, we use weak linkage so it can be overwritten. -#[link_section = ".code_signature"] -#[linkage = "weak"] -#[used] -static CODE_SIGNATURE: vex_sdk::vcodesig = - vex_sdk::vcodesig { magic: u32::from_le_bytes(*b"XVX5"), r#type: 0, owner: 2, options: 0 }; - -// This function is needed by the panic runtime. The symbol is named in -// pre-link args for the target specification, so keep that in sync. -#[cfg(not(test))] -#[no_mangle] -// NB. used by both libunwind and libpanic_abort -pub extern "C" fn __rust_abort() -> ! { - abort_internal() -} - // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} diff --git a/library/std/src/sys/stdio/mod.rs b/library/std/src/sys/stdio/mod.rs index 7436e4d9de4da..404ac87792696 100644 --- a/library/std/src/sys/stdio/mod.rs +++ b/library/std/src/sys/stdio/mod.rs @@ -29,6 +29,10 @@ cfg_select! { mod uefi; pub use uefi::*; } + target_os = "vexos" => { + mod vexos; + pub use vexos::*; + } all(target_os = "wasi", target_env = "p1") => { mod wasip1; pub use wasip1::*; diff --git a/library/std/src/sys/pal/vexos/stdio.rs b/library/std/src/sys/stdio/vexos.rs similarity index 62% rename from library/std/src/sys/pal/vexos/stdio.rs rename to library/std/src/sys/stdio/vexos.rs index 94c834843806f..60148103ae9e3 100644 --- a/library/std/src/sys/pal/vexos/stdio.rs +++ b/library/std/src/sys/stdio/vexos.rs @@ -1,14 +1,14 @@ use crate::io; -pub struct Stdin(()); -pub struct Stdout(()); -pub struct Stderr(()); +pub struct Stdin; +pub struct Stdout; +pub type Stderr = Stdout; -pub const STDIO_CHANNEL: u32 = 1; +const STDIO_CHANNEL: u32 = 1; impl Stdin { pub const fn new() -> Stdin { - Stdin(()) + Stdin } } @@ -34,7 +34,7 @@ impl io::Read for Stdin { impl Stdout { pub const fn new() -> Stdout { - Stdout(()) + Stdout } } @@ -64,38 +64,6 @@ impl io::Write for Stdout { } } -impl Stderr { - pub const fn new() -> Stderr { - Stderr(()) - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - let written = - unsafe { vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, buf.as_ptr(), buf.len() as u32) }; - - if written < 0 { - return Err(io::Error::new( - io::ErrorKind::Uncategorized, - "Internal write error occurred.", - )); - } - - Ok(written as usize) - } - - fn flush(&mut self) -> io::Result<()> { - unsafe { - while (vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) as usize) != STDOUT_BUF_SIZE { - vex_sdk::vexTasksRun(); - } - } - - Ok(()) - } -} - pub const STDIN_BUF_SIZE: usize = 4096; pub const STDOUT_BUF_SIZE: usize = 2048; From 2fdd96dd76ef1fcc40467f1b91c7ab04fbdb76fd Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sun, 24 Aug 2025 18:01:37 -0500 Subject: [PATCH 03/23] vexos: implement global dlmalloc fallback --- library/std/src/sys/alloc/mod.rs | 3 ++ library/std/src/sys/alloc/vexos.rs | 66 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 library/std/src/sys/alloc/vexos.rs diff --git a/library/std/src/sys/alloc/mod.rs b/library/std/src/sys/alloc/mod.rs index 6d4b09494a3f5..2045b2fecc6ac 100644 --- a/library/std/src/sys/alloc/mod.rs +++ b/library/std/src/sys/alloc/mod.rs @@ -92,6 +92,9 @@ cfg_select! { target_os = "uefi" => { mod uefi; } + target_os = "vexos" => { + mod vexos; + } target_family = "wasm" => { mod wasm; } diff --git a/library/std/src/sys/alloc/vexos.rs b/library/std/src/sys/alloc/vexos.rs new file mode 100644 index 0000000000000..12ada8be39264 --- /dev/null +++ b/library/std/src/sys/alloc/vexos.rs @@ -0,0 +1,66 @@ +// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint +#![allow(static_mut_refs)] + +use crate::alloc::{GlobalAlloc, Layout, System}; + +static mut DLMALLOC: dlmalloc::Dlmalloc = dlmalloc::Dlmalloc::new(); + +#[stable(feature = "alloc_system_type", since = "1.28.0")] +unsafe impl GlobalAlloc for System { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // Calling malloc() is safe because preconditions on this function match the trait method preconditions. + let _lock = lock::lock(); + unsafe { DLMALLOC.malloc(layout.size(), layout.align()) } + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // Calling calloc() is safe because preconditions on this function match the trait method preconditions. + let _lock = lock::lock(); + unsafe { DLMALLOC.calloc(layout.size(), layout.align()) } + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // Calling free() is safe because preconditions on this function match the trait method preconditions. + let _lock = lock::lock(); + unsafe { DLMALLOC.free(ptr, layout.size(), layout.align()) } + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // Calling realloc() is safe because preconditions on this function match the trait method preconditions. + let _lock = lock::lock(); + unsafe { DLMALLOC.realloc(ptr, layout.size(), layout.align(), new_size) } + } +} + +mod lock { + use crate::sync::atomic::Ordering::{Acquire, Release}; + use crate::sync::atomic::{Atomic, AtomicI32}; + + static LOCKED: Atomic = AtomicI32::new(0); + + pub struct DropLock; + + pub fn lock() -> DropLock { + loop { + if LOCKED.swap(1, Acquire) == 0 { + return DropLock; + } + crate::os::xous::ffi::do_yield(); + } + } + + impl Drop for DropLock { + fn drop(&mut self) { + let r = LOCKED.swap(0, Release); + debug_assert_eq!(r, 1); + } + } +} From a141abf8368443291e3e46c35a4fb0d838019083 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:56:12 -0500 Subject: [PATCH 04/23] vexos: finish bringing modules up to date --- library/std/Cargo.toml | 2 +- library/std/src/sys/alloc/vexos.rs | 1 - library/std/src/sys/fs/vexos.rs | 50 ++++++++++++++----------- library/std/src/sys/pal/vexos/mod.rs | 36 +++++++++--------- library/std/src/sys/pal/vexos/thread.rs | 22 +++++++++-- library/std/src/sys/pal/vexos/time.rs | 15 ++++++-- library/std/src/sys/thread_local/mod.rs | 1 + 7 files changed, 80 insertions(+), 47 deletions(-) diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index d6db90bdedab6..bf409ef898294 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -62,7 +62,7 @@ path = "../windows_targets" rand = { version = "0.9.0", default-features = false, features = ["alloc"] } rand_xorshift = "0.4.0" -[target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), target_os = "xous", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] +[target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), target_os = "xous", target_os = "vexos", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] dlmalloc = { version = "0.2.10", features = ['rustc-dep-of-std'] } [target.x86_64-fortanix-unknown-sgx.dependencies] diff --git a/library/std/src/sys/alloc/vexos.rs b/library/std/src/sys/alloc/vexos.rs index 12ada8be39264..b3cefe32c890e 100644 --- a/library/std/src/sys/alloc/vexos.rs +++ b/library/std/src/sys/alloc/vexos.rs @@ -53,7 +53,6 @@ mod lock { if LOCKED.swap(1, Acquire) == 0 { return DropLock; } - crate::os::xous::ffi::do_yield(); } } diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 914c03dbcf6db..e590e22e3fc87 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -1,10 +1,11 @@ use crate::ffi::{CString, OsString}; use crate::fmt; +use crate::fs::TryLockError; use crate::hash::Hash; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::path::{Path, PathBuf}; use crate::sys::time::SystemTime; -use crate::sys::unsupported; +use crate::sys::{unsupported, unsupported_err}; #[derive(Debug)] struct FileDesc(*mut vex_sdk::FIL); @@ -19,12 +20,8 @@ pub struct FileAttr { is_dir: bool, } -#[derive(Debug)] -pub struct ReadDir { - entries: Vec, -} +pub struct ReadDir(!); -#[derive(Debug)] pub struct DirEntry { path: PathBuf, } @@ -39,18 +36,18 @@ pub struct OpenOptions { } #[derive(Copy, Clone, Debug, Default)] -pub struct FileTimes(()); +pub struct FileTimes {} -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FilePermissions; +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions {} -#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct FileType { is_dir: bool, } #[derive(Debug)] -pub struct DirBuilder(()); +pub struct DirBuilder {} impl FileAttr { /// Creates a FileAttr by getting data from an opened file. @@ -79,9 +76,8 @@ impl FileAttr { let mut opts = OpenOptions::new(); opts.read(true); let file = File::open(path, &opts)?; - let fd = file.fd.0; - Self::from_fd(fd) + Self::from_fd(file.fd.0) } } @@ -90,7 +86,7 @@ impl FileAttr { } pub fn perm(&self) -> FilePermissions { - FilePermissions + FilePermissions {} } pub fn file_type(&self) -> FileType { @@ -135,15 +131,22 @@ impl FileType { } pub fn is_symlink(&self) -> bool { + // No symlinks in vexos; entries are either files or directories. false } } +impl fmt::Debug for ReadDir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0 + } +} + impl Iterator for ReadDir { type Item = io::Result; fn next(&mut self) -> Option> { - self.entries.pop().map(Ok) + self.0 } } @@ -191,6 +194,7 @@ impl OpenOptions { } impl File { + // TODO pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { // Mount sdcard volume as FAT filesystem map_fresult(unsafe { vex_sdk::vexFileMountSD() })?; @@ -265,12 +269,12 @@ impl File { unsupported() } - pub fn try_lock(&self) -> io::Result { - unsupported() + pub fn try_lock(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) } - pub fn try_lock_shared(&self) -> io::Result { - unsupported() + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) } pub fn unlock(&self) -> io::Result<()> { @@ -333,13 +337,17 @@ impl File { Ok(()) } - fn tell(&self) -> io::Result { + pub fn tell(&self) -> io::Result { let position = unsafe { vex_sdk::vexFileTell(self.fd.0) }; position.try_into().map_err(|_| { io::Error::new(io::ErrorKind::InvalidData, "Failed to get current location in file") }) } + pub fn size(&self) -> Option> { + None + } + pub fn seek(&self, pos: SeekFrom) -> io::Result { const SEEK_SET: i32 = 0; const SEEK_CUR: i32 = 1; @@ -412,7 +420,7 @@ impl File { impl DirBuilder { pub fn new() -> DirBuilder { - DirBuilder(()) + DirBuilder {} } pub fn mkdir(&self, _p: &Path) -> io::Result<()> { diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs index c8d4a28c4aa7b..06b64d1ffcac7 100644 --- a/library/std/src/sys/pal/vexos/mod.rs +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -21,9 +21,9 @@ global_asm!( ); #[cfg(not(test))] -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn _start() -> ! { - extern "C" { + unsafe extern "C" { static mut __bss_start: u8; static mut __bss_end: u8; @@ -40,6 +40,7 @@ pub unsafe extern "C" fn _start() -> ! { main(); + cleanup(); abort_internal() } @@ -49,7 +50,22 @@ 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 unsafe fn cleanup() { + let exit_time = Instant::now(); + const FLUSH_TIMEOUT: Duration = Duration::from_millis(15); + const STDIO_CHANNEL: u32 = 1; + + // Force the serial buffer to flush + while exit_time.elapsed() < FLUSH_TIMEOUT { + vex_sdk::vexTasksRun(); + + // If the buffer has been fully flushed, exit the loop + if vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) == (crate::sys::stdio::STDOUT_BUF_SIZE as i32) + { + break; + } + } +} pub fn unsupported() -> crate::io::Result { Err(unsupported_err()) @@ -68,21 +84,7 @@ pub fn decode_error_kind(_code: i32) -> crate::io::ErrorKind { } pub fn abort_internal() -> ! { - let exit_time = Instant::now(); - const FLUSH_TIMEOUT: Duration = Duration::from_millis(15); - unsafe { - // Force the serial buffer to flush - while exit_time.elapsed() < FLUSH_TIMEOUT { - vex_sdk::vexTasksRun(); - - // If the buffer has been fully flushed, exit the loop - if vex_sdk::vexSerialWriteFree(stdio::STDIO_CHANNEL) == (stdio::STDOUT_BUF_SIZE as i32) - { - break; - } - } - vex_sdk::vexSystemExitRequest(); loop { diff --git a/library/std/src/sys/pal/vexos/thread.rs b/library/std/src/sys/pal/vexos/thread.rs index f00ff9f7b1066..112fba920ab6b 100644 --- a/library/std/src/sys/pal/vexos/thread.rs +++ b/library/std/src/sys/pal/vexos/thread.rs @@ -4,13 +4,17 @@ use crate::io; use crate::num::NonZero; use crate::time::{Duration, Instant}; -pub struct Thread(!); - pub const DEFAULT_MIN_STACK_SIZE: usize = 64 * 1024; +pub struct Thread(!); + impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(_stack: usize, _p: Box) -> io::Result { + pub unsafe fn new( + _stack: usize, + _name: Option<&str>, + _p: Box, + ) -> io::Result { unsupported() } @@ -34,11 +38,23 @@ impl Thread { } } + pub fn sleep_until(deadline: Instant) { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + Self::sleep(delay); + } + } + pub fn join(self) { self.0 } } +pub(crate) fn current_os_id() -> Option { + None +} + pub fn available_parallelism() -> io::Result> { unsupported() } diff --git a/library/std/src/sys/pal/vexos/time.rs b/library/std/src/sys/pal/vexos/time.rs index 60805ea2759d0..2c269037df126 100644 --- a/library/std/src/sys/pal/vexos/time.rs +++ b/library/std/src/sys/pal/vexos/time.rs @@ -32,15 +32,22 @@ impl SystemTime { panic!("system time not implemented on this platform") } - pub fn sub_time(&self, other: &SystemTime) -> Result { - self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn sub_time(&self, other: &SystemTime) -> Result { + // FIXME: ok_or_else with const closures + match self.0.checked_sub(other.0) { + Some(duration) => Ok(duration), + None => Err(other.0 - self.0), + } } - pub fn checked_add_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_add_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_add(*other)?)) } - pub fn checked_sub_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_sub_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_sub(*other)?)) } } diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index 944f2dc6ae38a..d5c795093cf04 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -99,6 +99,7 @@ pub(crate) mod guard { target_os = "uefi", target_os = "zkvm", target_os = "trusty", + target_os = "vexos", ) => { pub(crate) fn enable() { // FIXME: Right now there is no concept of "thread exit" on From 36d71f130b5e7d97f8695165f5cd2066455d420c Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:19:17 -0500 Subject: [PATCH 05/23] vexos: provide `dlmalloc::Allocator` implementation --- library/std/src/sys/alloc/vexos.rs | 55 +++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/library/std/src/sys/alloc/vexos.rs b/library/std/src/sys/alloc/vexos.rs index b3cefe32c890e..1410fabaed912 100644 --- a/library/std/src/sys/alloc/vexos.rs +++ b/library/std/src/sys/alloc/vexos.rs @@ -2,8 +2,61 @@ #![allow(static_mut_refs)] use crate::alloc::{GlobalAlloc, Layout, System}; +use crate::ptr; +use crate::sync::atomic::{AtomicBool, Ordering}; -static mut DLMALLOC: dlmalloc::Dlmalloc = dlmalloc::Dlmalloc::new(); +// symbols defined in the target linkerscript +unsafe extern "C" { + static mut __heap_start: u8; + static mut __heap_end: u8; +} + +static mut DLMALLOC: dlmalloc::Dlmalloc = dlmalloc::Dlmalloc::new_with_allocator(Vexos); + +struct Vexos; + +unsafe impl dlmalloc::Allocator for Vexos { + /// Allocs system resources + fn alloc(&self, _size: usize) -> (*mut u8, usize, u32) { + static INIT: AtomicBool = AtomicBool::new(false); + + if !INIT.swap(true, Ordering::Relaxed) { + unsafe { + ( + (&raw mut __heap_start).cast(), + (&raw const __heap_end).byte_offset_from(ptr::addr_of!(__heap_start)) as _, + 0, + ) + } + } else { + (ptr::null_mut(), 0, 0) + } + } + + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + ptr::null_mut() + } + + fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool { + false + } + + fn free(&self, _ptr: *mut u8, _size: usize) -> bool { + return false; + } + + fn can_release_part(&self, _flags: u32) -> bool { + false + } + + fn allocates_zeros(&self) -> bool { + false + } + + fn page_size(&self) -> usize { + 0x1000 + } +} #[stable(feature = "alloc_system_type", since = "1.28.0")] unsafe impl GlobalAlloc for System { From 3989b2ca1a5fea3bdc7263b9cb21a73253cf44d2 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:19:48 -0500 Subject: [PATCH 06/23] vexos: block on stdout writes with size>2048 --- library/std/src/sys/pal/vexos/mod.rs | 4 ++-- library/std/src/sys/stdio/vexos.rs | 32 +++++++++++++++++++--------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs index 06b64d1ffcac7..47d09843e8744 100644 --- a/library/std/src/sys/pal/vexos/mod.rs +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -5,6 +5,7 @@ pub mod pipe; pub mod thread; pub mod time; +use crate::sys::stdio; use crate::arch::global_asm; use crate::ptr::{self, addr_of_mut}; use crate::time::{Duration, Instant}; @@ -53,14 +54,13 @@ pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} pub unsafe fn cleanup() { let exit_time = Instant::now(); const FLUSH_TIMEOUT: Duration = Duration::from_millis(15); - const STDIO_CHANNEL: u32 = 1; // Force the serial buffer to flush while exit_time.elapsed() < FLUSH_TIMEOUT { vex_sdk::vexTasksRun(); // If the buffer has been fully flushed, exit the loop - if vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) == (crate::sys::stdio::STDOUT_BUF_SIZE as i32) + if vex_sdk::vexSerialWriteFree(stdio::STDIO_CHANNEL) == (stdio::STDOUT_BUF_SIZE as i32) { break; } diff --git a/library/std/src/sys/stdio/vexos.rs b/library/std/src/sys/stdio/vexos.rs index 60148103ae9e3..4543fce3a2635 100644 --- a/library/std/src/sys/stdio/vexos.rs +++ b/library/std/src/sys/stdio/vexos.rs @@ -4,7 +4,7 @@ pub struct Stdin; pub struct Stdout; pub type Stderr = Stdout; -const STDIO_CHANNEL: u32 = 1; +pub const STDIO_CHANNEL: u32 = 1; impl Stdin { pub const fn new() -> Stdin { @@ -40,17 +40,29 @@ impl Stdout { impl io::Write for Stdout { fn write(&mut self, buf: &[u8]) -> io::Result { - let written = - unsafe { vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, buf.as_ptr(), buf.len() as u32) }; - - if written < 0 { - return Err(io::Error::new( - io::ErrorKind::Uncategorized, - "Internal write error occurred.", - )); + let mut count = 0; + + // HACK: VEXos holds an internal write buffer for serial that is flushed to USB1 roughly every + // millisecond by `vexTasksRun`. For writes larger than 2048 bytes, we must block until that buffer + // is flushed to USB1 before writing the rest of `buf`. In practice, this is fairly nonstandard for + // a `write` implementation but it avoids an guaranteed recursive panic when using macros such as + // `print!` to write large amounts of data to stdout at once. + for chunk in buf.chunks(STDOUT_BUF_SIZE) { + if unsafe { vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) as usize } < chunk.len() { + self.flush().unwrap(); + } + + count += unsafe { vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, chunk.as_ptr(), chunk.len() as u32) }; + + if count < 0 { + return Err(io::Error::new( + io::ErrorKind::Uncategorized, + "Internal write error occurred.", + )); + } } - Ok(written as usize) + Ok(count as usize) } fn flush(&mut self) -> io::Result<()> { From 0163635ba6f35a18f7bce825093cec22d2eb9da7 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:40:07 -0500 Subject: [PATCH 07/23] format, tidy --- library/std/src/sys/fs/vexos.rs | 1 - library/std/src/sys/pal/vexos/mod.rs | 5 ++-- library/std/src/sys/stdio/vexos.rs | 40 +++++++++++++++++++--------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index e590e22e3fc87..b20c056cb25b0 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -194,7 +194,6 @@ impl OpenOptions { } impl File { - // TODO pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { // Mount sdcard volume as FAT filesystem map_fresult(unsafe { vex_sdk::vexFileMountSD() })?; diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs index 47d09843e8744..97787365b4c69 100644 --- a/library/std/src/sys/pal/vexos/mod.rs +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -5,9 +5,9 @@ pub mod pipe; pub mod thread; pub mod time; -use crate::sys::stdio; use crate::arch::global_asm; use crate::ptr::{self, addr_of_mut}; +use crate::sys::stdio; use crate::time::{Duration, Instant}; global_asm!( @@ -60,8 +60,7 @@ pub unsafe fn cleanup() { vex_sdk::vexTasksRun(); // If the buffer has been fully flushed, exit the loop - if vex_sdk::vexSerialWriteFree(stdio::STDIO_CHANNEL) == (stdio::STDOUT_BUF_SIZE as i32) - { + if vex_sdk::vexSerialWriteFree(stdio::STDIO_CHANNEL) == (stdio::STDOUT_BUF_SIZE as i32) { break; } } diff --git a/library/std/src/sys/stdio/vexos.rs b/library/std/src/sys/stdio/vexos.rs index 4543fce3a2635..aebaf17984f7e 100644 --- a/library/std/src/sys/stdio/vexos.rs +++ b/library/std/src/sys/stdio/vexos.rs @@ -14,7 +14,7 @@ impl Stdin { impl io::Read for Stdin { fn read(&mut self, mut buf: &mut [u8]) -> io::Result { - let mut written = 0; + let mut count = 0; while let Some((out_byte, new_buf)) = buf.split_first_mut() { buf = new_buf; @@ -25,10 +25,10 @@ impl io::Read for Stdin { } *out_byte = byte as u8; - written += 1; + count += 1; } - Ok(written) + Ok(count) } } @@ -40,32 +40,48 @@ impl Stdout { impl io::Write for Stdout { fn write(&mut self, buf: &[u8]) -> io::Result { - let mut count = 0; + let mut written = 0; - // HACK: VEXos holds an internal write buffer for serial that is flushed to USB1 roughly every - // millisecond by `vexTasksRun`. For writes larger than 2048 bytes, we must block until that buffer - // is flushed to USB1 before writing the rest of `buf`. In practice, this is fairly nonstandard for - // a `write` implementation but it avoids an guaranteed recursive panic when using macros such as - // `print!` to write large amounts of data to stdout at once. + // HACK: VEXos holds an internal ringbuffer for serial writes that is flushed to USB1 + // roughly every millisecond by `vexTasksRun`. For writes larger than 2048 bytes, we + // must block until that buffer is flushed to USB1 before writing the rest of `buf`. + // + // This is fairly nonstandard for a `write` implementation, but it avoids a guaranteed + // recursive panic when using macros such as `print!` to write large amounts of data + // (buf.len() > 2048) to stdout at once. for chunk in buf.chunks(STDOUT_BUF_SIZE) { if unsafe { vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) as usize } < chunk.len() { self.flush().unwrap(); } - count += unsafe { vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, chunk.as_ptr(), chunk.len() as u32) }; - + let count = unsafe { + vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, chunk.as_ptr(), chunk.len() as u32) + }; if count < 0 { return Err(io::Error::new( io::ErrorKind::Uncategorized, "Internal write error occurred.", )); } + + written += count; + + // This is a sanity check to ensure that we don't end up with non-contiguous + // buffer writes. e.g. a chunk gets only partially written, but we continue + // attempting to write the remaining chunks. + // + // In practice, this should never really occur since the previous flush ensures + // enough space in FIFO to write the entire chunk to vexSerialWriteBuffer. + if count != chunk.len() { + break; + } } - Ok(count as usize) + Ok(written as usize) } fn flush(&mut self) -> io::Result<()> { + // This may block for up to a millisecond. unsafe { while (vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) as usize) != STDOUT_BUF_SIZE { vex_sdk::vexTasksRun(); From ba084972b0b1f91e3fbc444f098673ba4aa883c8 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:52:50 -0500 Subject: [PATCH 08/23] bump to vex-sdk 0.27.0 --- library/Cargo.lock | 3 ++- library/Cargo.toml | 2 -- library/std/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/library/Cargo.lock b/library/Cargo.lock index 6ad330d920ce6..e7669b6ec3510 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -394,7 +394,8 @@ dependencies = [ [[package]] name = "vex-sdk" version = "0.27.0" -source = "git+https://github.com/vexide/vex-sdk.git#67771319f730557693b2aa1e84c7ecd4831efb13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f74fce61d7a7ba1589da9634c6305a72befb7cc9150c1f872d87d8060f32b9" dependencies = [ "rustc-std-workspace-core", ] diff --git a/library/Cargo.toml b/library/Cargo.toml index 74a6ef26baeb8..e30e624094285 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -59,5 +59,3 @@ rustflags = ["-Cpanic=abort"] rustc-std-workspace-core = { path = 'rustc-std-workspace-core' } rustc-std-workspace-alloc = { path = 'rustc-std-workspace-alloc' } rustc-std-workspace-std = { path = 'rustc-std-workspace-std' } - -vex-sdk = { git = "https://github.com/vexide/vex-sdk.git" } diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index bf409ef898294..960c7e0a2c17d 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -90,7 +90,7 @@ r-efi = { version = "5.2.0", features = ['rustc-dep-of-std'] } r-efi-alloc = { version = "2.0.0", features = ['rustc-dep-of-std'] } [target.'cfg(target_os = "vexos")'.dependencies] -vex-sdk = { git = "https://github.com/vexide/vex-sdk.git", features = [ +vex-sdk = { version = "0.27.0", features = [ 'rustc-dep-of-std', ], default-features = false } From bed0127e365f76c98ca09a078a8ef07f06e12bc5 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:55:32 -0500 Subject: [PATCH 09/23] vexos: simplify logic by casting `count` to usize early --- library/std/src/sys/stdio/vexos.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/std/src/sys/stdio/vexos.rs b/library/std/src/sys/stdio/vexos.rs index aebaf17984f7e..339082f3e3049 100644 --- a/library/std/src/sys/stdio/vexos.rs +++ b/library/std/src/sys/stdio/vexos.rs @@ -56,6 +56,7 @@ impl io::Write for Stdout { let count = unsafe { vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, chunk.as_ptr(), chunk.len() as u32) + as usize; }; if count < 0 { return Err(io::Error::new( @@ -77,7 +78,7 @@ impl io::Write for Stdout { } } - Ok(written as usize) + Ok(written) } fn flush(&mut self) -> io::Result<()> { From 64a440fdba19566a11cb3daec0efae693700f6cf Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:10:55 -0500 Subject: [PATCH 10/23] vexos: remove accidental semicolon --- library/std/src/sys/stdio/vexos.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/stdio/vexos.rs b/library/std/src/sys/stdio/vexos.rs index 339082f3e3049..445c3c296ef33 100644 --- a/library/std/src/sys/stdio/vexos.rs +++ b/library/std/src/sys/stdio/vexos.rs @@ -56,8 +56,8 @@ impl io::Write for Stdout { let count = unsafe { vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, chunk.as_ptr(), chunk.len() as u32) - as usize; - }; + } as usize; + if count < 0 { return Err(io::Error::new( io::ErrorKind::Uncategorized, From 85025ad86c0f6c6b754c8c97bdb1ce06af4809d1 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Wed, 27 Aug 2025 23:18:41 -0500 Subject: [PATCH 11/23] vexos: fix file opening logic --- library/std/src/sys/fs/vexos.rs | 111 +++++++++++++++++++------------- 1 file changed, 68 insertions(+), 43 deletions(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index b20c056cb25b0..589d7ed46f0be 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -32,6 +32,7 @@ pub struct OpenOptions { write: bool, append: bool, truncate: bool, + create: bool, create_new: bool, } @@ -131,7 +132,7 @@ impl FileType { } pub fn is_symlink(&self) -> bool { - // No symlinks in vexos; entries are either files or directories. + // No symlinks in VEXos - entries are either files or directories. false } } @@ -170,7 +171,14 @@ impl DirEntry { impl OpenOptions { pub fn new() -> OpenOptions { - OpenOptions { read: false, write: false, append: false, truncate: false, create_new: false } + OpenOptions { + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + } } pub fn read(&mut self, read: bool) { @@ -186,7 +194,7 @@ impl OpenOptions { self.truncate = truncate; } pub fn create(&mut self, create: bool) { - self.write = create; + self.create = create; } pub fn create_new(&mut self, create_new: bool) { self.create_new = create_new; @@ -195,51 +203,65 @@ impl OpenOptions { impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { - // Mount sdcard volume as FAT filesystem - map_fresult(unsafe { vex_sdk::vexFileMountSD() })?; - let path = CString::new(path.as_os_str().as_encoded_bytes()).map_err(|_| { io::Error::new(io::ErrorKind::InvalidData, "Path contained a null byte") })?; - if opts.write && opts.read { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Files cannot be opened with read and write access", - )); - } - if opts.create_new { - let file_exists = unsafe { vex_sdk::vexFileStatus(path.as_ptr()) }; - if file_exists != 0 { - return Err(io::Error::new(io::ErrorKind::AlreadyExists, "File already exists")); - } - } + let file = + match (opts.read, opts.write, opts.append, opts.truncate, opts.create, opts.create_new) + { + // read + write - unsupported + (true, true, _, _, _, _) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Opening files in both read and write mode is unsupported", + )); + } - let file = if opts.read && !opts.write { - // The second argument to this function is ignored. - // Open in read only mode - unsafe { vex_sdk::vexFileOpen(path.as_ptr(), c"".as_ptr()) } - } else if opts.write && opts.append { - // Open in write and append mode - unsafe { vex_sdk::vexFileOpenWrite(path.as_ptr()) } - } else if opts.write && opts.truncate { - // Open in write mode - unsafe { vex_sdk::vexFileOpenCreate(path.as_ptr()) } - } else if opts.write { - unsafe { - // Open in read/write and append mode - let fd = vex_sdk::vexFileOpenWrite(path.as_ptr()); - // Seek to beginning of the file - vex_sdk::vexFileSeek(fd, 0, 0); - - fd - } - } else { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Files cannot be opened without read or write access", - )); - }; + // read + (true, false, _, false, false, false) => unsafe { + vex_sdk::vexFileOpen(path.as_ptr(), c"".as_ptr()) + }, + + // append + (false, _, true, false, create, create_new) => unsafe { + if create_new && vex_sdk::vexFileStatus(path.as_ptr()) != 0 { + return Err(io::Error::new(io::ErrorKind::AlreadyExists, "File exists")); + } else if !create && vex_sdk::vexFileStatus(path.as_ptr()) == 0 { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "No such file or directory", + )); + } + + vex_sdk::vexFileOpenWrite(path.as_ptr()) + }, + + // write + (false, true, false, truncate, create, create_new) => unsafe { + if create_new && vex_sdk::vexFileStatus(path.as_ptr()) != 0 { + return Err(io::Error::new(io::ErrorKind::AlreadyExists, "File exists")); + } else if !create && vex_sdk::vexFileStatus(path.as_ptr()) == 0 { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "No such file or directory", + )); + } + + if truncate { + unsafe { vex_sdk::vexFileOpenCreate(path.as_ptr()) } + } else { + // Open in append, but jump to the start of the file. + let fd = vex_sdk::vexFileOpenWrite(path.as_ptr()); + vex_sdk::vexFileSeek(fd, 0, 0); + fd + } + }, + + _ => { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid argument")); + } + }; if file.is_null() { Err(io::Error::new(io::ErrorKind::NotFound, "Could not open file")) @@ -288,6 +310,7 @@ impl File { let len = buf.len() as _; let buf_ptr = buf.as_mut_ptr(); let read = unsafe { vex_sdk::vexFileRead(buf_ptr.cast(), 1, len, self.fd.0) }; + if read < 0 { Err(io::Error::new(io::ErrorKind::Other, "Could not read from file")) } else { @@ -313,6 +336,7 @@ impl File { let buf_ptr = buf.as_ptr(); let written = unsafe { vex_sdk::vexFileWrite(buf_ptr.cast_mut().cast(), 1, len as _, self.fd.0) }; + if written < 0 { Err(io::Error::new(io::ErrorKind::Other, "Could not write to file")) } else { @@ -338,6 +362,7 @@ impl File { pub fn tell(&self) -> io::Result { let position = unsafe { vex_sdk::vexFileTell(self.fd.0) }; + position.try_into().map_err(|_| { io::Error::new(io::ErrorKind::InvalidData, "Failed to get current location in file") }) From 7e66bd1a03458e176c6d47d421e8768afb964ca5 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Wed, 27 Aug 2025 23:20:08 -0500 Subject: [PATCH 12/23] vexos: improve error message when opening files in RW mode --- library/std/src/sys/fs/vexos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 589d7ed46f0be..25ba02583944d 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -214,7 +214,7 @@ impl File { (true, true, _, _, _, _) => { return Err(io::Error::new( io::ErrorKind::InvalidInput, - "Opening files in both read and write mode is unsupported", + "Opening files with read and write access is unsupported on this target", )); } From 4be4d686eee3a8abd2414fb4552c85929b179245 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:03:34 -0500 Subject: [PATCH 13/23] vexos: fix logic regarding `create` and `create_new` --- library/std/src/sys/fs/vexos.rs | 42 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 25ba02583944d..08f7e6e15a78f 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -225,13 +225,20 @@ impl File { // append (false, _, true, false, create, create_new) => unsafe { - if create_new && vex_sdk::vexFileStatus(path.as_ptr()) != 0 { - return Err(io::Error::new(io::ErrorKind::AlreadyExists, "File exists")); - } else if !create && vex_sdk::vexFileStatus(path.as_ptr()) == 0 { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "No such file or directory", - )); + if create_new { + if vex_sdk::vexFileStatus(path.as_ptr()) != 0 { + return Err(io::Error::new( + io::ErrorKind::AlreadyExists, + "File exists", + )); + } + } else if !create { + if vex_sdk::vexFileStatus(path.as_ptr()) == 0 { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "No such file or directory", + )); + } } vex_sdk::vexFileOpenWrite(path.as_ptr()) @@ -239,13 +246,20 @@ impl File { // write (false, true, false, truncate, create, create_new) => unsafe { - if create_new && vex_sdk::vexFileStatus(path.as_ptr()) != 0 { - return Err(io::Error::new(io::ErrorKind::AlreadyExists, "File exists")); - } else if !create && vex_sdk::vexFileStatus(path.as_ptr()) == 0 { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "No such file or directory", - )); + if create_new { + if vex_sdk::vexFileStatus(path.as_ptr()) != 0 { + return Err(io::Error::new( + io::ErrorKind::AlreadyExists, + "File exists", + )); + } + } else if !create { + if vex_sdk::vexFileStatus(path.as_ptr()) == 0 { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "No such file or directory", + )); + } } if truncate { From 21ef17d7f7e26bea4d1002f50ed0e509b1ff6d25 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:28:53 -0500 Subject: [PATCH 14/23] update `armv7a-vex-v5` target documentation --- .../src/platform-support/armv7a-vex-v5.md | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/doc/rustc/src/platform-support/armv7a-vex-v5.md b/src/doc/rustc/src/platform-support/armv7a-vex-v5.md index a7da1b16f7e3d..e092c04de7e3e 100644 --- a/src/doc/rustc/src/platform-support/armv7a-vex-v5.md +++ b/src/doc/rustc/src/platform-support/armv7a-vex-v5.md @@ -4,7 +4,7 @@ Allows compiling user programs for the [VEX V5 Brain](https://www.vexrobotics.com/276-4810.html), a microcontroller for educational and competitive robotics. -Rust support for this target is not affiliated with VEX Robotics or IFI. +Rust support for this target is not affiliated with VEX Robotics or IFI, and does not link to any official VEX SDK. ## Target maintainers @@ -17,11 +17,24 @@ This target is maintained by members of the [vexide](https://github.com/vexide) ## Requirements -This target is cross-compiled and currently requires `#![no_std]`. Dynamic linking is unsupported. +This target is cross-compiled. Dynamic linking is unsupported. + +`#![no_std]` crates can be built using `build-std` to build `core` and `panic_abort` and optionally `alloc`. Unwinding panics are not yet supported on this target. + +`std` has only partial support due platform limitations. Notably: +- `std::process` and `std::net` are unimplemented. `std::thread` only supports sleeping and yielding, as this is a single-threaded environment. +- `std::time` has full support for `Instant`, but no support for `SystemTime`. +- `std::io` has full support for `stdin`/`stdout`/`stderr`. `stdout` and `stderr` both write to to USB channel 1 on this platform and are not differentiated. +- `std::fs`, has limited support for reading or writing to files. Directory operations, file deletion, and some file opening features are unsupported and will return errors. +- A global allocator is provided and implemented on top of `dlmalloc`. +- Modules that do not need to interact with the OS beyond allocation, such as `std::collections`, `std::hash`, `std::future`, `std::sync`, etc are fully supported. +- Random number generation is insecure, as there is no reliable source of entropy on this platform. + +In order to support some APIs, users are expected to provide a supporting runtime SDK for `libstd` to link against. This library may be provided either by [`vex-sdk-build`](https://github.com/vexide/vex-sdk/tree/main/packages/vex-sdk-build) (which will download an official SDK from VEX) or through an open-source implementation such as [`vex-sdk-jumptable`](https://crates.io/crates/vex-sdk-jumptable). When compiling for this target, the "C" calling convention maps to AAPCS with VFP registers (hard float ABI) and the "system" calling convention maps to AAPCS without VFP registers (soft float ABI). -This target generates binaries in the ELF format that may uploaded to the brain with external tools. +This target generates binaries in the ELF format that may be uploaded to the brain with external tools. ## Building the target @@ -29,10 +42,7 @@ You can build Rust with support for this target by adding it to the `target` lis ## Building Rust programs -Rust does not yet ship pre-compiled artifacts for this target. To compile for -this target, you will either need to build Rust with the target enabled (see -"Building the target" above), or build your own copy of `core` by using -`build-std` or similar. +Rust does not yet ship pre-compiled artifacts for this target. To compile for this target, you will either need to build Rust with the target enabled (see "Building the target" above), or build your own copy of `core` by using `build-std` or similar. When the compiler builds a binary, an ELF build artifact will be produced. Additional tools are required for this artifact to be recognizable to VEXos as a user program. From 6b85cc90a97cc6f932bf6a56b6f67867bb9a14e1 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:31:28 -0500 Subject: [PATCH 15/23] clarify details regarding system ABI, fix typos --- src/doc/rustc/src/platform-support/armv7a-vex-v5.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc/src/platform-support/armv7a-vex-v5.md b/src/doc/rustc/src/platform-support/armv7a-vex-v5.md index e092c04de7e3e..3677f8931dd68 100644 --- a/src/doc/rustc/src/platform-support/armv7a-vex-v5.md +++ b/src/doc/rustc/src/platform-support/armv7a-vex-v5.md @@ -25,14 +25,14 @@ This target is cross-compiled. Dynamic linking is unsupported. - `std::process` and `std::net` are unimplemented. `std::thread` only supports sleeping and yielding, as this is a single-threaded environment. - `std::time` has full support for `Instant`, but no support for `SystemTime`. - `std::io` has full support for `stdin`/`stdout`/`stderr`. `stdout` and `stderr` both write to to USB channel 1 on this platform and are not differentiated. -- `std::fs`, has limited support for reading or writing to files. Directory operations, file deletion, and some file opening features are unsupported and will return errors. -- A global allocator is provided and implemented on top of `dlmalloc`. -- Modules that do not need to interact with the OS beyond allocation, such as `std::collections`, `std::hash`, `std::future`, `std::sync`, etc are fully supported. -- Random number generation is insecure, as there is no reliable source of entropy on this platform. +- `std::fs` has limited support for reading or writing to files. Directory operations, file deletion, and some file opening features are unsupported and will return errors. +- A global allocator implemented on top of `dlmalloc` is provided. +- Modules that do not need to interact with the OS beyond allocation such as `std::collections`, `std::hash`, `std::future`, `std::sync`, etc are fully supported. +- Random number generation and hashing is insecure, as there is no reliable source of entropy on this platform. In order to support some APIs, users are expected to provide a supporting runtime SDK for `libstd` to link against. This library may be provided either by [`vex-sdk-build`](https://github.com/vexide/vex-sdk/tree/main/packages/vex-sdk-build) (which will download an official SDK from VEX) or through an open-source implementation such as [`vex-sdk-jumptable`](https://crates.io/crates/vex-sdk-jumptable). -When compiling for this target, the "C" calling convention maps to AAPCS with VFP registers (hard float ABI) and the "system" calling convention maps to AAPCS without VFP registers (soft float ABI). +When compiling for this target, the "C" calling convention maps to AAPCS with VFP registers (hard float ABI) and the "system" calling convention maps to AAPCS without VFP registers (softfp ABI). This target generates binaries in the ELF format that may be uploaded to the brain with external tools. From 70804e49cb5ccb59b8d7220723cf08ca3080ae61 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Thu, 28 Aug 2025 17:42:03 -0500 Subject: [PATCH 16/23] simplify `vexFileStatus` check in `fs::exists` --- library/std/src/sys/fs/vexos.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 08f7e6e15a78f..e38b4f51ec626 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -512,8 +512,7 @@ pub fn exists(path: &Path) -> io::Result { let path = CString::new(path.as_os_str().as_encoded_bytes()) .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Path contained a null byte"))?; - let file_exists = unsafe { vex_sdk::vexFileStatus(path.as_ptr()) }; - if file_exists != 0 { Ok(true) } else { Ok(false) } + Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0) } pub fn readlink(_p: &Path) -> io::Result { From c3a6373b73fa43bd82a1bb24ed9cc2c9cc72282e Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Thu, 28 Aug 2025 17:50:00 -0500 Subject: [PATCH 17/23] remove unnecessary allocator lock on VEXos --- library/std/src/sys/alloc/vexos.rs | 40 ++++++------------------------ 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/library/std/src/sys/alloc/vexos.rs b/library/std/src/sys/alloc/vexos.rs index 1410fabaed912..3cbba6b377cbe 100644 --- a/library/std/src/sys/alloc/vexos.rs +++ b/library/std/src/sys/alloc/vexos.rs @@ -62,57 +62,33 @@ unsafe impl dlmalloc::Allocator for Vexos { unsafe impl GlobalAlloc for System { #[inline] unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // guarantees unique and non-reentrant access to the allocator. // Calling malloc() is safe because preconditions on this function match the trait method preconditions. - let _lock = lock::lock(); unsafe { DLMALLOC.malloc(layout.size(), layout.align()) } } #[inline] unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { - // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // guarantees unique and non-reentrant access to the allocator. // Calling calloc() is safe because preconditions on this function match the trait method preconditions. - let _lock = lock::lock(); unsafe { DLMALLOC.calloc(layout.size(), layout.align()) } } #[inline] unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // guarantees unique and non-reentrant access to the allocator. // Calling free() is safe because preconditions on this function match the trait method preconditions. - let _lock = lock::lock(); unsafe { DLMALLOC.free(ptr, layout.size(), layout.align()) } } #[inline] unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { - // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // guarantees unique and non-reentrant access to the allocator. // Calling realloc() is safe because preconditions on this function match the trait method preconditions. - let _lock = lock::lock(); unsafe { DLMALLOC.realloc(ptr, layout.size(), layout.align(), new_size) } } } - -mod lock { - use crate::sync::atomic::Ordering::{Acquire, Release}; - use crate::sync::atomic::{Atomic, AtomicI32}; - - static LOCKED: Atomic = AtomicI32::new(0); - - pub struct DropLock; - - pub fn lock() -> DropLock { - loop { - if LOCKED.swap(1, Acquire) == 0 { - return DropLock; - } - } - } - - impl Drop for DropLock { - fn drop(&mut self) { - let r = LOCKED.swap(0, Release); - debug_assert_eq!(r, 1); - } - } -} From 7eb898ed94eda12a9cb58f6fe20cecf78337839a Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Thu, 28 Aug 2025 17:55:39 -0500 Subject: [PATCH 18/23] fmt --- library/std/src/sys/alloc/vexos.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/std/src/sys/alloc/vexos.rs b/library/std/src/sys/alloc/vexos.rs index 3cbba6b377cbe..916d380fe8e35 100644 --- a/library/std/src/sys/alloc/vexos.rs +++ b/library/std/src/sys/alloc/vexos.rs @@ -62,7 +62,7 @@ unsafe impl dlmalloc::Allocator for Vexos { unsafe impl GlobalAlloc for System { #[inline] unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which // guarantees unique and non-reentrant access to the allocator. // Calling malloc() is safe because preconditions on this function match the trait method preconditions. unsafe { DLMALLOC.malloc(layout.size(), layout.align()) } @@ -70,7 +70,7 @@ unsafe impl GlobalAlloc for System { #[inline] unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { - // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which // guarantees unique and non-reentrant access to the allocator. // Calling calloc() is safe because preconditions on this function match the trait method preconditions. unsafe { DLMALLOC.calloc(layout.size(), layout.align()) } @@ -78,7 +78,7 @@ unsafe impl GlobalAlloc for System { #[inline] unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which // guarantees unique and non-reentrant access to the allocator. // Calling free() is safe because preconditions on this function match the trait method preconditions. unsafe { DLMALLOC.free(ptr, layout.size(), layout.align()) } @@ -86,7 +86,7 @@ unsafe impl GlobalAlloc for System { #[inline] unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { - // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which // guarantees unique and non-reentrant access to the allocator. // Calling realloc() is safe because preconditions on this function match the trait method preconditions. unsafe { DLMALLOC.realloc(ptr, layout.size(), layout.align(), new_size) } From 7f8cb7d3a586e58a68e32a51ecb2df04cdfbafdf Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:43:43 -0500 Subject: [PATCH 19/23] switch to `run_path_with_cstr` for path conversion --- library/std/src/sys/fs/vexos.rs | 65 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index e38b4f51ec626..6350150a89a86 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -4,6 +4,7 @@ use crate::fs::TryLockError; use crate::hash::Hash; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::path::{Path, PathBuf}; +use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::time::SystemTime; use crate::sys::{unsupported, unsupported_err}; @@ -63,23 +64,21 @@ impl FileAttr { } fn from_path(path: &Path) -> io::Result { - let c_path = CString::new(path.as_os_str().as_encoded_bytes()).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidData, "Path contained a null byte") - })?; - - let file_type = unsafe { vex_sdk::vexFileStatus(c_path.as_ptr()) }; - let is_dir = file_type == 3; - - // We can't get the size if its a directory because we cant open it as a file - if is_dir { - Ok(Self { size: 0, is_dir: true }) - } else { - let mut opts = OpenOptions::new(); - opts.read(true); - let file = File::open(path, &opts)?; - - Self::from_fd(file.fd.0) - } + run_path_with_cstr(path, &|c_path| { + let file_type = unsafe { vex_sdk::vexFileStatus(c_path.as_ptr()) }; + let is_dir = file_type == 3; + + // We can't get the size if its a directory because we cant open it as a file + if is_dir { + Ok(Self { size: 0, is_dir: true }) + } else { + let mut opts = OpenOptions::new(); + opts.read(true); + let file = File::open(path, &opts)?; + + Self::from_fd(file.fd.0) + } + }) } pub fn size(&self) -> u64 { @@ -203,13 +202,15 @@ impl OpenOptions { impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { - let path = CString::new(path.as_os_str().as_encoded_bytes()).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidData, "Path contained a null byte") - })?; - - let file = - match (opts.read, opts.write, opts.append, opts.truncate, opts.create, opts.create_new) - { + run_path_with_cstr(path, &|path| { + let file = match ( + opts.read, + opts.write, + opts.append, + opts.truncate, + opts.create, + opts.create_new, + ) { // read + write - unsupported (true, true, _, _, _, _) => { return Err(io::Error::new( @@ -277,11 +278,12 @@ impl File { } }; - if file.is_null() { - Err(io::Error::new(io::ErrorKind::NotFound, "Could not open file")) - } else { - Ok(Self { fd: FileDesc(file) }) - } + if file.is_null() { + Err(io::Error::new(io::ErrorKind::NotFound, "Could not open file")) + } else { + Ok(Self { fd: FileDesc(file) }) + } + }) } pub fn file_attr(&self) -> io::Result { @@ -509,10 +511,7 @@ pub fn remove_dir_all(_path: &Path) -> io::Result<()> { } pub fn exists(path: &Path) -> io::Result { - let path = CString::new(path.as_os_str().as_encoded_bytes()) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Path contained a null byte"))?; - - Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0) + run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0)) } pub fn readlink(_p: &Path) -> io::Result { From 76b70baf37e2c3031f20318eb836c31bf8ccfaa0 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Fri, 29 Aug 2025 21:09:44 -0500 Subject: [PATCH 20/23] vexos: refactor `FileAttr`, clean up `open` logic --- library/std/src/sys/fs/vexos.rs | 70 ++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 6350150a89a86..2f5d39a90cfc0 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -16,9 +16,9 @@ pub struct File { } #[derive(Clone)] -pub struct FileAttr { - size: u64, - is_dir: bool, +pub enum FileAttr { + Dir, + File { size: u64 }, } pub struct ReadDir(!); @@ -54,23 +54,24 @@ pub struct DirBuilder {} impl FileAttr { /// Creates a FileAttr by getting data from an opened file. fn from_fd(fd: *mut vex_sdk::FIL) -> io::Result { - let size = unsafe { vex_sdk::vexFileSize(fd) }; - - if size >= 0 { - Ok(Self { size: size as u64, is_dir: false }) + // `vexFileSize` returns -1 upon error, so u64::try_from will fail on error. + if let Some(size) = u64::try_from(unsafe { vex_sdk::vexFileSize(fd) }) { + Ok(Self::File { size }) } else { Err(io::Error::new(io::ErrorKind::InvalidData, "Failed to get file size")) } } fn from_path(path: &Path) -> io::Result { + // vexFileStatus returns 3 if the given path is a directory. + const FILE_STATUS_DIR: i32 = 3; + run_path_with_cstr(path, &|c_path| { let file_type = unsafe { vex_sdk::vexFileStatus(c_path.as_ptr()) }; - let is_dir = file_type == 3; // We can't get the size if its a directory because we cant open it as a file - if is_dir { - Ok(Self { size: 0, is_dir: true }) + if file_type == FILE_STATUS_DIR { + Ok(Self::Dir) } else { let mut opts = OpenOptions::new(); opts.read(true); @@ -82,7 +83,10 @@ impl FileAttr { } pub fn size(&self) -> u64 { - self.size + match self { + Self::File { size } => size, + Self::Dir => 0, + } } pub fn perm(&self) -> FilePermissions { @@ -90,7 +94,7 @@ impl FileAttr { } pub fn file_type(&self) -> FileType { - FileType { is_dir: self.is_dir } + self == FileAttr::Dir } pub fn modified(&self) -> io::Result { @@ -156,7 +160,7 @@ impl DirEntry { } pub fn file_name(&self) -> OsString { - self.path.file_name().unwrap_or(crate::ffi::OsStr::new("")).to_os_string() + self.path.file_name().unwrap_or_default() } pub fn metadata(&self) -> io::Result { @@ -203,16 +207,9 @@ impl OpenOptions { impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { run_path_with_cstr(path, &|path| { - let file = match ( - opts.read, - opts.write, - opts.append, - opts.truncate, - opts.create, - opts.create_new, - ) { + let file = match opts { // read + write - unsupported - (true, true, _, _, _, _) => { + OpenOptions { read: true, write: true, .. } => { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Opening files with read and write access is unsupported on this target", @@ -220,12 +217,24 @@ impl File { } // read - (true, false, _, false, false, false) => unsafe { - vex_sdk::vexFileOpen(path.as_ptr(), c"".as_ptr()) - }, + OpenOptions { + read: true, + write: false, + append: _, + truncate: false, + create: false, + create_new: false, + } => unsafe { vex_sdk::vexFileOpen(path.as_ptr(), c"".as_ptr()) }, // append - (false, _, true, false, create, create_new) => unsafe { + OpenOptions { + read: false, + write: _, + append: true, + truncate: false, + create, + create_new, + } => unsafe { if create_new { if vex_sdk::vexFileStatus(path.as_ptr()) != 0 { return Err(io::Error::new( @@ -246,7 +255,14 @@ impl File { }, // write - (false, true, false, truncate, create, create_new) => unsafe { + OpenOptions { + read: false, + write: true, + append: false, + truncate, + create, + create_new, + } => unsafe { if create_new { if vex_sdk::vexFileStatus(path.as_ptr()) != 0 { return Err(io::Error::new( From 4cc8721fc08b2f7229e5bcc10bcd482589744bce Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Fri, 29 Aug 2025 21:15:28 -0500 Subject: [PATCH 21/23] vexos: improve unsafe hygiene in alloc and PAL, fix fs issues --- library/std/src/sys/alloc/vexos.rs | 16 +++++++++------- library/std/src/sys/fs/vexos.rs | 22 +++++++++++----------- library/std/src/sys/pal/vexos/mod.rs | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/library/std/src/sys/alloc/vexos.rs b/library/std/src/sys/alloc/vexos.rs index 916d380fe8e35..c1fb6896a89ae 100644 --- a/library/std/src/sys/alloc/vexos.rs +++ b/library/std/src/sys/alloc/vexos.rs @@ -5,7 +5,7 @@ use crate::alloc::{GlobalAlloc, Layout, System}; use crate::ptr; use crate::sync::atomic::{AtomicBool, Ordering}; -// symbols defined in the target linkerscript +// Symbols for heap section boundaries defined in the target's linkerscript unsafe extern "C" { static mut __heap_start: u8; static mut __heap_end: u8; @@ -21,10 +21,12 @@ unsafe impl dlmalloc::Allocator for Vexos { static INIT: AtomicBool = AtomicBool::new(false); if !INIT.swap(true, Ordering::Relaxed) { + // This target has no growable heap, as user memory has a fixed + // size/location and VEXos does not manage allocation for us. unsafe { ( - (&raw mut __heap_start).cast(), - (&raw const __heap_end).byte_offset_from(ptr::addr_of!(__heap_start)) as _, + (&raw mut __heap_start).cast::(), + (&raw const __heap_end).offset_from_unsigned(&raw const __heap_start), 0, ) } @@ -63,7 +65,7 @@ unsafe impl GlobalAlloc for System { #[inline] unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which - // guarantees unique and non-reentrant access to the allocator. + // guarantees unique and non-reentrant access to the allocator. As such, no allocator lock is used. // Calling malloc() is safe because preconditions on this function match the trait method preconditions. unsafe { DLMALLOC.malloc(layout.size(), layout.align()) } } @@ -71,7 +73,7 @@ unsafe impl GlobalAlloc for System { #[inline] unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which - // guarantees unique and non-reentrant access to the allocator. + // guarantees unique and non-reentrant access to the allocator. As such, no allocator lock is used. // Calling calloc() is safe because preconditions on this function match the trait method preconditions. unsafe { DLMALLOC.calloc(layout.size(), layout.align()) } } @@ -79,7 +81,7 @@ unsafe impl GlobalAlloc for System { #[inline] unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which - // guarantees unique and non-reentrant access to the allocator. + // guarantees unique and non-reentrant access to the allocator. As such, no allocator lock is used. // Calling free() is safe because preconditions on this function match the trait method preconditions. unsafe { DLMALLOC.free(ptr, layout.size(), layout.align()) } } @@ -87,7 +89,7 @@ unsafe impl GlobalAlloc for System { #[inline] unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which - // guarantees unique and non-reentrant access to the allocator. + // guarantees unique and non-reentrant access to the allocator. As such, no allocator lock is used. // Calling realloc() is safe because preconditions on this function match the trait method preconditions. unsafe { DLMALLOC.realloc(ptr, layout.size(), layout.align(), new_size) } } diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 2f5d39a90cfc0..cf73604e95216 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -55,7 +55,7 @@ impl FileAttr { /// Creates a FileAttr by getting data from an opened file. fn from_fd(fd: *mut vex_sdk::FIL) -> io::Result { // `vexFileSize` returns -1 upon error, so u64::try_from will fail on error. - if let Some(size) = u64::try_from(unsafe { vex_sdk::vexFileSize(fd) }) { + if let Ok(size) = u64::try_from(unsafe { vex_sdk::vexFileSize(fd) }) { Ok(Self::File { size }) } else { Err(io::Error::new(io::ErrorKind::InvalidData, "Failed to get file size")) @@ -64,7 +64,7 @@ impl FileAttr { fn from_path(path: &Path) -> io::Result { // vexFileStatus returns 3 if the given path is a directory. - const FILE_STATUS_DIR: i32 = 3; + const FILE_STATUS_DIR: u32 = 3; run_path_with_cstr(path, &|c_path| { let file_type = unsafe { vex_sdk::vexFileStatus(c_path.as_ptr()) }; @@ -84,7 +84,7 @@ impl FileAttr { pub fn size(&self) -> u64 { match self { - Self::File { size } => size, + Self::File { size } => *size, Self::Dir => 0, } } @@ -94,7 +94,7 @@ impl FileAttr { } pub fn file_type(&self) -> FileType { - self == FileAttr::Dir + FileType { is_dir: matches!(self, FileAttr::Dir) } } pub fn modified(&self) -> io::Result { @@ -160,7 +160,7 @@ impl DirEntry { } pub fn file_name(&self) -> OsString { - self.path.file_name().unwrap_or_default() + self.path.file_name().unwrap_or_default().into() } pub fn metadata(&self) -> io::Result { @@ -235,14 +235,14 @@ impl File { create, create_new, } => unsafe { - if create_new { + if *create_new { if vex_sdk::vexFileStatus(path.as_ptr()) != 0 { return Err(io::Error::new( io::ErrorKind::AlreadyExists, "File exists", )); } - } else if !create { + } else if !*create { if vex_sdk::vexFileStatus(path.as_ptr()) == 0 { return Err(io::Error::new( io::ErrorKind::NotFound, @@ -263,14 +263,14 @@ impl File { create, create_new, } => unsafe { - if create_new { + if *create_new { if vex_sdk::vexFileStatus(path.as_ptr()) != 0 { return Err(io::Error::new( io::ErrorKind::AlreadyExists, "File exists", )); } - } else if !create { + } else if !*create { if vex_sdk::vexFileStatus(path.as_ptr()) == 0 { return Err(io::Error::new( io::ErrorKind::NotFound, @@ -279,7 +279,7 @@ impl File { } } - if truncate { + if *truncate { unsafe { vex_sdk::vexFileOpenCreate(path.as_ptr()) } } else { // Open in append, but jump to the start of the file. @@ -434,7 +434,7 @@ impl File { // we have to calculate the offset from the end of the file ourselves. map_fresult(vex_sdk::vexFileSeek( self.fd.0, - try_convert_offset(self.file_attr()?.size as i64 + offset)?, + try_convert_offset(self.file_attr()?.size() as i64 + offset)?, SEEK_SET, ))? } diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs index 97787365b4c69..d412052826848 100644 --- a/library/std/src/sys/pal/vexos/mod.rs +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -36,7 +36,7 @@ pub unsafe extern "C" fn _start() -> ! { ptr::write_bytes( &raw mut __bss_start, 0, - (&raw mut __bss_end).offset_from(&raw mut __bss_start) as usize, + (&raw mut __bss_end).offset_from_unsigned(&raw mut __bss_start), ); main(); From 9f67126624561c551c90c2de9b728ad22873c763 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Fri, 29 Aug 2025 21:24:35 -0500 Subject: [PATCH 22/23] re-export relevant items from `unsupported` in vexos modules --- library/std/src/sys/fs/vexos.rs | 124 ++---------------------- library/std/src/sys/pal/vexos/thread.rs | 16 ++- library/std/src/sys/pal/vexos/time.rs | 36 ++----- 3 files changed, 21 insertions(+), 155 deletions(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index cf73604e95216..e65c72a2c3788 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -8,6 +8,15 @@ use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::time::SystemTime; use crate::sys::{unsupported, unsupported_err}; +#[expect(dead_code)] +#[path = "unsupported.rs"] +mod unsupported_fs; + +pub use unsupported_fs::{ + DirBuilder, FilePermissions, FileTimes, ReadDir, canonicalize, link, remove_dir_all, rename, + rmdir, set_perm, symlink, unlink, +}; + #[derive(Debug)] struct FileDesc(*mut vex_sdk::FIL); @@ -21,8 +30,6 @@ pub enum FileAttr { File { size: u64 }, } -pub struct ReadDir(!); - pub struct DirEntry { path: PathBuf, } @@ -37,20 +44,11 @@ pub struct OpenOptions { create_new: bool, } -#[derive(Copy, Clone, Debug, Default)] -pub struct FileTimes {} - -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct FilePermissions {} - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct FileType { is_dir: bool, } -#[derive(Debug)] -pub struct DirBuilder {} - impl FileAttr { /// Creates a FileAttr by getting data from an opened file. fn from_fd(fd: *mut vex_sdk::FIL) -> io::Result { @@ -110,21 +108,6 @@ impl FileAttr { } } -impl FilePermissions { - pub fn readonly(&self) -> bool { - false - } - - pub fn set_readonly(&mut self, _readonly: bool) { - panic!("Perimissions do not exist") - } -} - -impl FileTimes { - pub fn set_accessed(&mut self, _t: SystemTime) {} - pub fn set_modified(&mut self, _t: SystemTime) {} -} - impl FileType { pub fn is_dir(&self) -> bool { self.is_dir @@ -140,38 +123,6 @@ impl FileType { } } -impl fmt::Debug for ReadDir { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0 - } -} - -impl Iterator for ReadDir { - type Item = io::Result; - - fn next(&mut self) -> Option> { - self.0 - } -} - -impl DirEntry { - pub fn path(&self) -> PathBuf { - self.path.clone() - } - - pub fn file_name(&self) -> OsString { - self.path.file_name().unwrap_or_default().into() - } - - pub fn metadata(&self) -> io::Result { - stat(&self.path) - } - - pub fn file_type(&self) -> io::Result { - Ok(self.metadata()?.file_type()) - } -} - impl OpenOptions { pub fn new() -> OpenOptions { OpenOptions { @@ -474,16 +425,6 @@ impl File { } } -impl DirBuilder { - pub fn new() -> DirBuilder { - DirBuilder {} - } - - pub fn mkdir(&self, _p: &Path) -> io::Result<()> { - unsupported() - } -} - impl fmt::Debug for File { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("File").field("fd", &self.fd.0).finish() @@ -495,53 +436,10 @@ impl Drop for File { } } -pub fn readdir(_p: &Path) -> io::Result { - // While there *is* a userspace function for reading file directories, - // the necessary implementation cannot currently be done cleanly, as - // VEXos does not expose directory length to user programs. - // - // This means that we would need to create a large fixed-length buffer - // and hope that the folder's contents didn't exceed that buffer's length, - // which obviously isn't behavior we want to rely on in the standard library. - unsupported() -} - -pub fn unlink(_p: &Path) -> io::Result<()> { - unsupported() -} - -pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { - unsupported() -} - -pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { - unsupported() -} - -pub fn rmdir(_p: &Path) -> io::Result<()> { - unsupported() -} - -pub fn remove_dir_all(_path: &Path) -> io::Result<()> { - unsupported() -} - pub fn exists(path: &Path) -> io::Result { run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0)) } -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(p: &Path) -> io::Result { FileAttr::from_path(p) } @@ -551,10 +449,6 @@ pub fn lstat(p: &Path) -> io::Result { stat(p) } -pub fn canonicalize(_p: &Path) -> io::Result { - unsupported() -} - pub fn copy(from: &Path, to: &Path) -> io::Result { use crate::fs::File; diff --git a/library/std/src/sys/pal/vexos/thread.rs b/library/std/src/sys/pal/vexos/thread.rs index 112fba920ab6b..806105c73a1ad 100644 --- a/library/std/src/sys/pal/vexos/thread.rs +++ b/library/std/src/sys/pal/vexos/thread.rs @@ -1,11 +1,15 @@ +#[expect(dead_code)] +#[path = "unsupported.rs"] +mod unsupported_thread; + +pub use unsupported_thread::{DEFAULT_MIN_STACK_SIZE, available_parallelism, current_os_id}; + use super::unsupported; use crate::ffi::CStr; use crate::io; use crate::num::NonZero; use crate::time::{Duration, Instant}; -pub const DEFAULT_MIN_STACK_SIZE: usize = 64 * 1024; - pub struct Thread(!); impl Thread { @@ -50,11 +54,3 @@ impl Thread { self.0 } } - -pub(crate) fn current_os_id() -> Option { - None -} - -pub fn available_parallelism() -> io::Result> { - unsupported() -} diff --git a/library/std/src/sys/pal/vexos/time.rs b/library/std/src/sys/pal/vexos/time.rs index 2c269037df126..2f3b5bda6c555 100644 --- a/library/std/src/sys/pal/vexos/time.rs +++ b/library/std/src/sys/pal/vexos/time.rs @@ -1,13 +1,14 @@ +#[expect(dead_code)] +#[path = "unsupported.rs"] +mod unsupported_time; + +pub use unsupported_time::{SystemTime, UNIX_EPOCH}; + use crate::time::Duration; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Instant(Duration); -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct SystemTime(Duration); - -pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); - impl Instant { pub fn now() -> Instant { let micros = unsafe { vex_sdk::vexSystemHighResTimeGet() }; @@ -26,28 +27,3 @@ impl Instant { Some(Instant(self.0.checked_sub(*other)?)) } } - -impl SystemTime { - pub fn now() -> SystemTime { - panic!("system time not implemented on this platform") - } - - #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] - pub const fn sub_time(&self, other: &SystemTime) -> Result { - // FIXME: ok_or_else with const closures - match self.0.checked_sub(other.0) { - Some(duration) => Ok(duration), - None => Err(other.0 - self.0), - } - } - - #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] - pub const fn checked_add_duration(&self, other: &Duration) -> Option { - Some(SystemTime(self.0.checked_add(*other)?)) - } - - #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] - pub const fn checked_sub_duration(&self, other: &Duration) -> Option { - Some(SystemTime(self.0.checked_sub(*other)?)) - } -} From fb1b204ba62148e1e11bd0e810d85c4f758a0610 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Fri, 29 Aug 2025 21:46:29 -0500 Subject: [PATCH 23/23] fix type incompatibilities from `unsupported` re-exports --- library/std/src/sys/fs/vexos.rs | 67 +++++++++++++++++++++++-- library/std/src/sys/pal/vexos/thread.rs | 15 +++--- library/std/src/sys/pal/vexos/time.rs | 7 ++- 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index e65c72a2c3788..301fdc948432c 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -11,10 +11,9 @@ use crate::sys::{unsupported, unsupported_err}; #[expect(dead_code)] #[path = "unsupported.rs"] mod unsupported_fs; - pub use unsupported_fs::{ - DirBuilder, FilePermissions, FileTimes, ReadDir, canonicalize, link, remove_dir_all, rename, - rmdir, set_perm, symlink, unlink, + DirBuilder, FileTimes, canonicalize, link, readlink, remove_dir_all, rename, rmdir, symlink, + unlink, }; #[derive(Debug)] @@ -30,6 +29,8 @@ pub enum FileAttr { File { size: u64 }, } +pub struct ReadDir(!); + pub struct DirEntry { path: PathBuf, } @@ -44,6 +45,9 @@ pub struct OpenOptions { create_new: bool, } +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions {} + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct FileType { is_dir: bool, @@ -108,6 +112,16 @@ impl FileAttr { } } +impl FilePermissions { + pub fn readonly(&self) -> bool { + false + } + + pub fn set_readonly(&mut self, _readonly: bool) { + panic!("Perimissions do not exist") + } +} + impl FileType { pub fn is_dir(&self) -> bool { self.is_dir @@ -123,6 +137,38 @@ impl FileType { } } +impl fmt::Debug for ReadDir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0 + } +} + +impl Iterator for ReadDir { + type Item = io::Result; + + fn next(&mut self) -> Option> { + self.0 + } +} + +impl DirEntry { + pub fn path(&self) -> PathBuf { + self.path.clone() + } + + pub fn file_name(&self) -> OsString { + self.path.file_name().unwrap_or_default().into() + } + + pub fn metadata(&self) -> io::Result { + stat(&self.path) + } + + pub fn file_type(&self) -> io::Result { + Ok(self.metadata()?.file_type()) + } +} + impl OpenOptions { pub fn new() -> OpenOptions { OpenOptions { @@ -436,6 +482,21 @@ impl Drop for File { } } +pub fn readdir(_p: &Path) -> io::Result { + // While there *is* a userspace function for reading file directories, + // the necessary implementation cannot currently be done cleanly, as + // VEXos does not expose directory length to user programs. + // + // This means that we would need to create a large fixed-length buffer + // and hope that the folder's contents didn't exceed that buffer's length, + // which obviously isn't behavior we want to rely on in the standard library. + unsupported() +} + +pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + unsupported() +} + pub fn exists(path: &Path) -> io::Result { run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0)) } diff --git a/library/std/src/sys/pal/vexos/thread.rs b/library/std/src/sys/pal/vexos/thread.rs index 806105c73a1ad..6f4874f03cb24 100644 --- a/library/std/src/sys/pal/vexos/thread.rs +++ b/library/std/src/sys/pal/vexos/thread.rs @@ -1,15 +1,14 @@ -#[expect(dead_code)] -#[path = "unsupported.rs"] -mod unsupported_thread; - -pub use unsupported_thread::{DEFAULT_MIN_STACK_SIZE, available_parallelism, current_os_id}; - use super::unsupported; use crate::ffi::CStr; use crate::io; use crate::num::NonZero; use crate::time::{Duration, Instant}; +#[expect(dead_code)] +#[path = "../unsupported/thread.rs"] +mod unsupported_thread; +pub use unsupported_thread::{DEFAULT_MIN_STACK_SIZE, available_parallelism}; + pub struct Thread(!); impl Thread { @@ -54,3 +53,7 @@ impl Thread { self.0 } } + +pub(crate) fn current_os_id() -> Option { + None +} diff --git a/library/std/src/sys/pal/vexos/time.rs b/library/std/src/sys/pal/vexos/time.rs index 2f3b5bda6c555..f95d96cd27ac0 100644 --- a/library/std/src/sys/pal/vexos/time.rs +++ b/library/std/src/sys/pal/vexos/time.rs @@ -1,11 +1,10 @@ +use crate::time::Duration; + #[expect(dead_code)] -#[path = "unsupported.rs"] +#[path = "../unsupported/time.rs"] mod unsupported_time; - pub use unsupported_time::{SystemTime, UNIX_EPOCH}; -use crate::time::Duration; - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Instant(Duration);