about summary refs log tree commit diff
path: root/library/std/src
diff options
context:
space:
mode:
Diffstat (limited to 'library/std/src')
-rw-r--r--library/std/src/env.rs2
-rw-r--r--library/std/src/ffi/os_str.rs2
-rw-r--r--library/std/src/io/mod.rs2
-rw-r--r--library/std/src/keyword_docs.rs35
-rw-r--r--library/std/src/lib.rs8
-rw-r--r--library/std/src/net/hostname.rs22
-rw-r--r--library/std/src/net/mod.rs6
-rw-r--r--library/std/src/net/socket_addr.rs2
-rw-r--r--library/std/src/num/f32.rs3
-rw-r--r--library/std/src/num/f64.rs3
-rw-r--r--library/std/src/os/net/linux_ext/tcp.rs24
-rw-r--r--library/std/src/os/net/linux_ext/tests.rs11
-rw-r--r--library/std/src/os/windows/ffi.rs2
-rw-r--r--library/std/src/panic.rs2
-rw-r--r--library/std/src/path.rs223
-rw-r--r--library/std/src/sys/alloc/mod.rs3
-rw-r--r--library/std/src/sys/alloc/vexos.rs96
-rw-r--r--library/std/src/sys/env_consts.rs11
-rw-r--r--library/std/src/sys/fs/mod.rs4
-rw-r--r--library/std/src/sys/fs/vexos.rs615
-rw-r--r--library/std/src/sys/net/connection/socket/unix.rs9
-rw-r--r--library/std/src/sys/net/connection/socket/windows.rs80
-rw-r--r--library/std/src/sys/net/hostname/mod.rs14
-rw-r--r--library/std/src/sys/net/hostname/unix.rs62
-rw-r--r--library/std/src/sys/net/hostname/unsupported.rs6
-rw-r--r--library/std/src/sys/net/hostname/windows.rs24
-rw-r--r--library/std/src/sys/net/mod.rs3
-rw-r--r--library/std/src/sys/pal/hermit/time.rs11
-rw-r--r--library/std/src/sys/pal/mod.rs4
-rw-r--r--library/std/src/sys/pal/uefi/helpers.rs4
-rw-r--r--library/std/src/sys/pal/unix/os.rs2
-rw-r--r--library/std/src/sys/pal/unix/stack_overflow.rs14
-rw-r--r--library/std/src/sys/pal/unix/time.rs27
-rw-r--r--library/std/src/sys/pal/vexos/mod.rs80
-rw-r--r--library/std/src/sys/pal/vexos/time.rs28
-rw-r--r--library/std/src/sys/pal/windows/c/bindings.txt2
-rw-r--r--library/std/src/sys/pal/windows/c/windows_sys.rs2
-rw-r--r--library/std/src/sys/pal/windows/mod.rs4
-rw-r--r--library/std/src/sys/pal/windows/winsock.rs80
-rw-r--r--library/std/src/sys/random/mod.rs2
-rw-r--r--library/std/src/sys/stdio/mod.rs4
-rw-r--r--library/std/src/sys/stdio/vexos.rs100
-rw-r--r--library/std/src/sys/thread/mod.rs7
-rw-r--r--library/std/src/sys/thread/vexos.rs17
-rw-r--r--library/std/src/sys/thread_local/mod.rs4
-rw-r--r--library/std/src/sys/thread_local/native/eager.rs4
-rw-r--r--library/std/src/sys/thread_local/native/lazy.rs4
-rw-r--r--library/std/src/sys/thread_local/native/mod.rs12
-rw-r--r--library/std/src/sys/thread_local/no_threads.rs69
-rw-r--r--library/std/src/sys/thread_local/os.rs155
-rw-r--r--library/std/src/thread/current.rs24
-rw-r--r--library/std/src/thread/local.rs233
-rw-r--r--library/std/src/thread/mod.rs5
53 files changed, 1947 insertions, 225 deletions
diff --git a/library/std/src/env.rs b/library/std/src/env.rs
index e457cd61c75..6d716bd8544 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 {
     ///
     /// <details><summary>Full list of possible values</summary>
     ///
+    /// * `"bin"`
     /// * `"exe"`
     /// * `"efi"`
     /// * `"js"`
diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs
index a39565d2159..6c098034eea 100644
--- a/library/std/src/ffi/os_str.rs
+++ b/library/std/src/ffi/os_str.rs
@@ -137,7 +137,7 @@ impl OsString {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[must_use]
     #[inline]
-    #[rustc_const_stable(feature = "const_pathbuf_osstring_new", since = "CURRENT_RUSTC_VERSION")]
+    #[rustc_const_stable(feature = "const_pathbuf_osstring_new", since = "1.91.0")]
     pub const fn new() -> OsString {
         OsString { inner: Buf::from_string(String::new()) }
     }
diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs
index a45edd08e8c..25a4661a0bc 100644
--- a/library/std/src/io/mod.rs
+++ b/library/std/src/io/mod.rs
@@ -3234,7 +3234,7 @@ fn inlined_slow_read_byte<R: Read>(reader: &mut R) -> Option<Result<u8>> {
     }
 }
 
-// Used by `BufReader::spec_read_byte`, for which the `inline(ever)` is
+// Used by `BufReader::spec_read_byte`, for which the `inline(never)` is
 // important.
 #[inline(never)]
 fn uninlined_slow_read_byte<R: Read>(reader: &mut R) -> Option<Result<u8>> {
diff --git a/library/std/src/keyword_docs.rs b/library/std/src/keyword_docs.rs
index 7ff4af8ede8..dc0d11b07a9 100644
--- a/library/std/src/keyword_docs.rs
+++ b/library/std/src/keyword_docs.rs
@@ -1,6 +1,8 @@
 #[doc(keyword = "as")]
 //
-/// Cast between types, or rename an import.
+/// Cast between types, rename an import, or qualify paths to associated items.
+///
+/// # Type casting
 ///
 /// `as` is most commonly used to turn primitive types into other primitive types, but it has other
 /// uses that include turning pointers into addresses, addresses into pointers, and pointers into
@@ -30,6 +32,8 @@
 /// `as *mut _` though the [`cast`][const-cast] method is recommended over `as *const _` and it is
 /// [the same][mut-cast] for `as *mut _`: those methods make the intent clearer.
 ///
+/// # Renaming imports
+///
 /// `as` is also used to rename imports in [`use`] and [`extern crate`][`crate`] statements:
 ///
 /// ```
@@ -37,9 +41,34 @@
 /// use std::{mem as memory, net as network};
 /// // Now you can use the names `memory` and `network` to refer to `std::mem` and `std::net`.
 /// ```
-/// For more information on what `as` is capable of, see the [Reference].
 ///
-/// [Reference]: ../reference/expressions/operator-expr.html#type-cast-expressions
+/// # Qualifying paths
+///
+/// You'll also find with `From` and `Into`, and indeed all traits, that `as` is used for the
+/// _fully qualified path_, a means of disambiguating associated items, i.e. functions,
+/// constants, and types.  For example, if you have a type which implements two traits with identical
+/// method names (e.g. `Into::<u32>::into` and `Into::<u64>::into`), you can clarify which method
+/// you'll use with `<MyThing as Into<u32>>::into(my_thing)`[^as-use-from].  This is quite verbose,
+/// but fortunately, Rust's type inference usually saves you from needing this, although it is
+/// occasionally necessary, especially with methods that return a generic type like `Into::into` or
+/// methods that don't take `self`.  It's more common to use in macros where it can provide necessary
+/// hygiene.
+///
+/// [^as-use-from]: You should probably never use this syntax with `Into` and instead write
+/// `T::from(my_thing)`.  It just happens that there aren't any great examples for this syntax in
+/// the standard library.  Also, at time of writing, the compiler tends to suggest fully-qualified
+/// paths to fix ambiguous `Into::into` calls, so the example should hopefully be familiar.
+///
+/// # Further reading
+///
+/// For more information on what `as` is capable of, see the Reference on [type cast expressions],
+/// [renaming imported entities], [renaming `extern` crates]
+/// and [qualified paths].
+///
+/// [type cast expressions]: ../reference/expressions/operator-expr.html#type-cast-expressions
+/// [renaming imported entities]: https://doc.rust-lang.org/reference/items/use-declarations.html#as-renames
+/// [renaming `extern` crates]: https://doc.rust-lang.org/reference/items/extern-crates.html#r-items.extern-crate.as
+/// [qualified paths]: ../reference/paths.html#qualified-paths
 /// [`crate`]: keyword.crate.html
 /// [`use`]: keyword.use.html
 /// [const-cast]: pointer::cast
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 45abd6bca8a..da41c1216c4 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -94,7 +94,7 @@
 //! pull-requests for your suggested changes.
 //!
 //! Contributions are appreciated! If you see a part of the docs that can be
-//! improved, submit a PR, or chat with us first on [Discord][rust-discord]
+//! improved, submit a PR, or chat with us first on [Zulip][rust-zulip]
 //! #docs.
 //!
 //! # A Tour of The Rust Standard Library
@@ -212,7 +212,7 @@
 //! [multithreading]: thread
 //! [other]: #what-is-in-the-standard-library-documentation
 //! [primitive types]: ../book/ch03-02-data-types.html
-//! [rust-discord]: https://discord.gg/rust-lang
+//! [rust-zulip]: https://rust-lang.zulipchat.com/
 //! [array]: prim@array
 //! [slice]: prim@slice
 
@@ -235,7 +235,7 @@
     test(attr(allow(dead_code, deprecated, unused_variables, unused_mut)))
 )]
 #![doc(rust_logo)]
-#![doc(cfg_hide(not(test), no_global_oom_handling, not(no_global_oom_handling)))]
+#![doc(auto_cfg(hide(no_global_oom_handling)))]
 // Don't link to std. We are std.
 #![no_std]
 // Tell the compiler to link to either panic_abort or panic_unwind
@@ -285,7 +285,6 @@
 #![feature(decl_macro)]
 #![feature(deprecated_suggestion)]
 #![feature(doc_cfg)]
-#![feature(doc_cfg_hide)]
 #![feature(doc_masked)]
 #![feature(doc_notable_trait)]
 #![feature(dropck_eyepatch)]
@@ -332,6 +331,7 @@
 #![feature(char_internals)]
 #![feature(clone_to_uninit)]
 #![feature(const_convert)]
+#![feature(const_mul_add)]
 #![feature(core_intrinsics)]
 #![feature(core_io_borrowed_buf)]
 #![feature(drop_guard)]
diff --git a/library/std/src/net/hostname.rs b/library/std/src/net/hostname.rs
new file mode 100644
index 00000000000..b1010cec600
--- /dev/null
+++ b/library/std/src/net/hostname.rs
@@ -0,0 +1,22 @@
+use crate::ffi::OsString;
+
+/// Returns the system hostname.
+///
+/// This can error out in platform-specific error cases;
+/// for example, uefi and wasm, where hostnames aren't
+/// supported.
+///
+/// # Underlying system calls
+///
+/// | Platform | System call                                                                                             |
+/// |----------|---------------------------------------------------------------------------------------------------------|
+/// | UNIX     | [`gethostname`](https://www.man7.org/linux/man-pages/man2/gethostname.2.html)                           |
+/// | Windows  | [`GetHostNameW`](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew) |
+///
+/// Note that platform-specific behavior [may change in the future][changes].
+///
+/// [changes]: crate::io#platform-specific-behavior
+#[unstable(feature = "gethostname", issue = "135142")]
+pub fn hostname() -> crate::io::Result<OsString> {
+    crate::sys::net::hostname()
+}
diff --git a/library/std/src/net/mod.rs b/library/std/src/net/mod.rs
index 40f1a93e39d..3e4447eb33f 100644
--- a/library/std/src/net/mod.rs
+++ b/library/std/src/net/mod.rs
@@ -1,7 +1,8 @@
 //! Networking primitives for TCP/UDP communication.
 //!
 //! This module provides networking functionality for the Transmission Control and User
-//! Datagram Protocols, as well as types for IP and socket addresses.
+//! Datagram Protocols, as well as types for IP and socket addresses and functions related
+//! to network properties.
 //!
 //! # Organization
 //!
@@ -24,6 +25,8 @@
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use core::net::AddrParseError;
 
+#[unstable(feature = "gethostname", issue = "135142")]
+pub use self::hostname::hostname;
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use self::ip_addr::{IpAddr, Ipv4Addr, Ipv6Addr, Ipv6MulticastScope};
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -35,6 +38,7 @@ pub use self::tcp::{Incoming, TcpListener, TcpStream};
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use self::udp::UdpSocket;
 
+mod hostname;
 mod ip_addr;
 mod socket_addr;
 mod tcp;
diff --git a/library/std/src/net/socket_addr.rs b/library/std/src/net/socket_addr.rs
index 5b56dd3f744..8214ad381f1 100644
--- a/library/std/src/net/socket_addr.rs
+++ b/library/std/src/net/socket_addr.rs
@@ -28,6 +28,8 @@ use crate::{io, iter, option, slice, vec};
 ///    [`SocketAddr`] as expected by its [`FromStr`] implementation or a string like
 ///    `<host_name>:<port>` pair where `<port>` is a [`u16`] value.
 ///
+///  * <code>&[[SocketAddr]]</code>: all [`SocketAddr`] values in the slice will be used.
+///
 /// This trait allows constructing network objects like [`TcpStream`] or
 /// [`UdpSocket`] easily with values of various types for the bind/connection
 /// address. It is needed because sometimes one type is more appropriate than
diff --git a/library/std/src/num/f32.rs b/library/std/src/num/f32.rs
index ac1d889cc37..e7810e77e76 100644
--- a/library/std/src/num/f32.rs
+++ b/library/std/src/num/f32.rs
@@ -217,7 +217,8 @@ impl f32 {
     #[must_use = "method returns a new number and does not mutate the original value"]
     #[stable(feature = "rust1", since = "1.0.0")]
     #[inline]
-    pub fn mul_add(self, a: f32, b: f32) -> f32 {
+    #[rustc_const_unstable(feature = "const_mul_add", issue = "146724")]
+    pub const fn mul_add(self, a: f32, b: f32) -> f32 {
         core::f32::math::mul_add(self, a, b)
     }
 
diff --git a/library/std/src/num/f64.rs b/library/std/src/num/f64.rs
index 55c8593a0c0..cbebbfb1be1 100644
--- a/library/std/src/num/f64.rs
+++ b/library/std/src/num/f64.rs
@@ -217,7 +217,8 @@ impl f64 {
     #[must_use = "method returns a new number and does not mutate the original value"]
     #[stable(feature = "rust1", since = "1.0.0")]
     #[inline]
-    pub fn mul_add(self, a: f64, b: f64) -> f64 {
+    #[rustc_const_unstable(feature = "const_mul_add", issue = "146724")]
+    pub const fn mul_add(self, a: f64, b: f64) -> f64 {
         core::f64::math::mul_add(self, a, b)
     }
 
diff --git a/library/std/src/os/net/linux_ext/tcp.rs b/library/std/src/os/net/linux_ext/tcp.rs
index fde53ec4257..3f9b2bd3f4b 100644
--- a/library/std/src/os/net/linux_ext/tcp.rs
+++ b/library/std/src/os/net/linux_ext/tcp.rs
@@ -4,6 +4,8 @@
 
 use crate::sealed::Sealed;
 use crate::sys_common::AsInner;
+#[cfg(target_os = "linux")]
+use crate::time::Duration;
 use crate::{io, net};
 
 /// Os-specific extensions for [`TcpStream`]
@@ -59,11 +61,13 @@ pub trait TcpStreamExt: Sealed {
 
     /// A socket listener will be awakened solely when data arrives.
     ///
-    /// The `accept` argument set the delay in seconds until the
+    /// The `accept` argument set the maximum delay until the
     /// data is available to read, reducing the number of short lived
     /// connections without data to process.
     /// Contrary to other platforms `SO_ACCEPTFILTER` feature equivalent, there is
     /// no necessity to set it after the `listen` call.
+    /// Note that the delay is expressed as Duration from user's perspective
+    /// the call rounds it down to the nearest second expressible as a `c_int`.
     ///
     /// See [`man 7 tcp`](https://man7.org/linux/man-pages/man7/tcp.7.html)
     ///
@@ -73,16 +77,17 @@ pub trait TcpStreamExt: Sealed {
     /// #![feature(tcp_deferaccept)]
     /// use std::net::TcpStream;
     /// use std::os::linux::net::TcpStreamExt;
+    /// use std::time::Duration;
     ///
     /// let stream = TcpStream::connect("127.0.0.1:8080")
     ///         .expect("Couldn't connect to the server...");
-    /// stream.set_deferaccept(1).expect("set_deferaccept call failed");
+    /// stream.set_deferaccept(Duration::from_secs(1u64)).expect("set_deferaccept call failed");
     /// ```
     #[unstable(feature = "tcp_deferaccept", issue = "119639")]
     #[cfg(target_os = "linux")]
-    fn set_deferaccept(&self, accept: u32) -> io::Result<()>;
+    fn set_deferaccept(&self, accept: Duration) -> io::Result<()>;
 
-    /// Gets the accept delay value (in seconds) of the `TCP_DEFER_ACCEPT` option.
+    /// Gets the accept delay value of the `TCP_DEFER_ACCEPT` option.
     ///
     /// For more information about this option, see [`TcpStreamExt::set_deferaccept`].
     ///
@@ -92,15 +97,16 @@ pub trait TcpStreamExt: Sealed {
     /// #![feature(tcp_deferaccept)]
     /// use std::net::TcpStream;
     /// use std::os::linux::net::TcpStreamExt;
+    /// use std::time::Duration;
     ///
     /// let stream = TcpStream::connect("127.0.0.1:8080")
     ///         .expect("Couldn't connect to the server...");
-    /// stream.set_deferaccept(1).expect("set_deferaccept call failed");
-    /// assert_eq!(stream.deferaccept().unwrap_or(0), 1);
+    /// stream.set_deferaccept(Duration::from_secs(1u64)).expect("set_deferaccept call failed");
+    /// assert_eq!(stream.deferaccept().unwrap(), Duration::from_secs(1u64));
     /// ```
     #[unstable(feature = "tcp_deferaccept", issue = "119639")]
     #[cfg(target_os = "linux")]
-    fn deferaccept(&self) -> io::Result<u32>;
+    fn deferaccept(&self) -> io::Result<Duration>;
 }
 
 #[stable(feature = "tcp_quickack", since = "1.89.0")]
@@ -117,12 +123,12 @@ impl TcpStreamExt for net::TcpStream {
     }
 
     #[cfg(target_os = "linux")]
-    fn set_deferaccept(&self, accept: u32) -> io::Result<()> {
+    fn set_deferaccept(&self, accept: Duration) -> io::Result<()> {
         self.as_inner().as_inner().set_deferaccept(accept)
     }
 
     #[cfg(target_os = "linux")]
-    fn deferaccept(&self) -> io::Result<u32> {
+    fn deferaccept(&self) -> io::Result<Duration> {
         self.as_inner().as_inner().deferaccept()
     }
 }
diff --git a/library/std/src/os/net/linux_ext/tests.rs b/library/std/src/os/net/linux_ext/tests.rs
index 12f35696abc..0758b426ccc 100644
--- a/library/std/src/os/net/linux_ext/tests.rs
+++ b/library/std/src/os/net/linux_ext/tests.rs
@@ -32,6 +32,7 @@ fn deferaccept() {
     use crate::net::test::next_test_ip4;
     use crate::net::{TcpListener, TcpStream};
     use crate::os::net::linux_ext::tcp::TcpStreamExt;
+    use crate::time::Duration;
 
     macro_rules! t {
         ($e:expr) => {
@@ -43,10 +44,12 @@ fn deferaccept() {
     }
 
     let addr = next_test_ip4();
+    let one = Duration::from_secs(1u64);
+    let zero = Duration::from_secs(0u64);
     let _listener = t!(TcpListener::bind(&addr));
     let stream = t!(TcpStream::connect(&("localhost", addr.port())));
-    stream.set_deferaccept(1).expect("set_deferaccept failed");
-    assert_eq!(stream.deferaccept().unwrap(), 1);
-    stream.set_deferaccept(0).expect("set_deferaccept failed");
-    assert_eq!(stream.deferaccept().unwrap(), 0);
+    stream.set_deferaccept(one).expect("set_deferaccept failed");
+    assert_eq!(stream.deferaccept().unwrap(), one);
+    stream.set_deferaccept(zero).expect("set_deferaccept failed");
+    assert_eq!(stream.deferaccept().unwrap(), zero);
 }
diff --git a/library/std/src/os/windows/ffi.rs b/library/std/src/os/windows/ffi.rs
index 345d5b74285..20e5383dc09 100644
--- a/library/std/src/os/windows/ffi.rs
+++ b/library/std/src/os/windows/ffi.rs
@@ -141,7 +141,7 @@ impl OsStrExt for OsStr {
 pub struct EncodeWide<'a> {
     inner: alloc::wtf8::EncodeWide<'a>,
 }
-#[stable(feature = "encode_wide_debug", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "encode_wide_debug", since = "1.91.0")]
 impl fmt::Debug for EncodeWide<'_> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         fmt::Debug::fmt(&self.inner, f)
diff --git a/library/std/src/panic.rs b/library/std/src/panic.rs
index 5e8d2f8e78e..1997785885d 100644
--- a/library/std/src/panic.rs
+++ b/library/std/src/panic.rs
@@ -122,7 +122,7 @@ impl<'a> PanicHookInfo<'a> {
     /// ```
     #[must_use]
     #[inline]
-    #[stable(feature = "panic_payload_as_str", since = "CURRENT_RUSTC_VERSION")]
+    #[stable(feature = "panic_payload_as_str", since = "1.91.0")]
     pub fn payload_as_str(&self) -> Option<&str> {
         if let Some(s) = self.payload.downcast_ref::<&str>() {
             Some(s)
diff --git a/library/std/src/path.rs b/library/std/src/path.rs
index 70ba502d684..6e3b1e6e47d 100644
--- a/library/std/src/path.rs
+++ b/library/std/src/path.rs
@@ -1191,7 +1191,7 @@ impl PathBuf {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[must_use]
     #[inline]
-    #[rustc_const_stable(feature = "const_pathbuf_osstring_new", since = "CURRENT_RUSTC_VERSION")]
+    #[rustc_const_stable(feature = "const_pathbuf_osstring_new", since = "1.91.0")]
     pub const fn new() -> PathBuf {
         PathBuf { inner: OsString::new() }
     }
@@ -1412,6 +1412,99 @@ impl PathBuf {
         }
     }
 
+    /// Sets whether the path has a trailing [separator](MAIN_SEPARATOR).
+    ///
+    /// The value returned by [`has_trailing_sep`](Path::has_trailing_sep) will be equivalent to
+    /// the provided value if possible.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(path_trailing_sep)]
+    /// use std::path::PathBuf;
+    ///
+    /// let mut p = PathBuf::from("dir");
+    ///
+    /// assert!(!p.has_trailing_sep());
+    /// p.set_trailing_sep(false);
+    /// assert!(!p.has_trailing_sep());
+    /// p.set_trailing_sep(true);
+    /// assert!(p.has_trailing_sep());
+    /// p.set_trailing_sep(false);
+    /// assert!(!p.has_trailing_sep());
+    ///
+    /// p = PathBuf::from("/");
+    /// assert!(p.has_trailing_sep());
+    /// p.set_trailing_sep(false);
+    /// assert!(p.has_trailing_sep());
+    /// ```
+    #[unstable(feature = "path_trailing_sep", issue = "142503")]
+    pub fn set_trailing_sep(&mut self, trailing_sep: bool) {
+        if trailing_sep { self.push_trailing_sep() } else { self.pop_trailing_sep() }
+    }
+
+    /// Adds a trailing [separator](MAIN_SEPARATOR) to the path.
+    ///
+    /// This acts similarly to [`Path::with_trailing_sep`], but mutates the underlying `PathBuf`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(path_trailing_sep)]
+    /// use std::ffi::OsStr;
+    /// use std::path::PathBuf;
+    ///
+    /// let mut p = PathBuf::from("dir");
+    ///
+    /// assert!(!p.has_trailing_sep());
+    /// p.push_trailing_sep();
+    /// assert!(p.has_trailing_sep());
+    /// p.push_trailing_sep();
+    /// assert!(p.has_trailing_sep());
+    ///
+    /// p = PathBuf::from("dir/");
+    /// p.push_trailing_sep();
+    /// assert_eq!(p.as_os_str(), OsStr::new("dir/"));
+    /// ```
+    #[unstable(feature = "path_trailing_sep", issue = "142503")]
+    pub fn push_trailing_sep(&mut self) {
+        if !self.has_trailing_sep() {
+            self.push("");
+        }
+    }
+
+    /// Removes a trailing [separator](MAIN_SEPARATOR) from the path, if possible.
+    ///
+    /// This acts similarly to [`Path::trim_trailing_sep`], but mutates the underlying `PathBuf`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(path_trailing_sep)]
+    /// use std::ffi::OsStr;
+    /// use std::path::PathBuf;
+    ///
+    /// let mut p = PathBuf::from("dir//");
+    ///
+    /// assert!(p.has_trailing_sep());
+    /// assert_eq!(p.as_os_str(), OsStr::new("dir//"));
+    /// p.pop_trailing_sep();
+    /// assert!(!p.has_trailing_sep());
+    /// assert_eq!(p.as_os_str(), OsStr::new("dir"));
+    /// p.pop_trailing_sep();
+    /// assert!(!p.has_trailing_sep());
+    /// assert_eq!(p.as_os_str(), OsStr::new("dir"));
+    ///
+    /// p = PathBuf::from("/");
+    /// assert!(p.has_trailing_sep());
+    /// p.pop_trailing_sep();
+    /// assert!(p.has_trailing_sep());
+    /// ```
+    #[unstable(feature = "path_trailing_sep", issue = "142503")]
+    pub fn pop_trailing_sep(&mut self) {
+        self.inner.truncate(self.trim_trailing_sep().as_os_str().len());
+    }
+
     /// Updates [`self.file_name`] to `file_name`.
     ///
     /// If [`self.file_name`] was [`None`], this is equivalent to pushing
@@ -1594,7 +1687,7 @@ impl PathBuf {
     /// p.add_extension("");
     /// assert_eq!(Path::new("/feel/the.formatted.dark"), p.as_path());
     /// ```
-    #[stable(feature = "path_add_extension", since = "CURRENT_RUSTC_VERSION")]
+    #[stable(feature = "path_add_extension", since = "1.91.0")]
     pub fn add_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
         self._add_extension(extension.as_ref())
     }
@@ -1610,7 +1703,7 @@ impl PathBuf {
         let new = extension.as_encoded_bytes();
         if !new.is_empty() {
             // truncate until right after the file name
-            // this is necessary for trimming the trailing slash
+            // this is necessary for trimming the trailing separator
             let end_file_name = file_name[file_name.len()..].as_ptr().addr();
             let start = self.inner.as_encoded_bytes().as_ptr().addr();
             self.inner.truncate(end_file_name.wrapping_sub(start));
@@ -2103,35 +2196,35 @@ impl PartialEq for PathBuf {
     }
 }
 
-#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "eq_str_for_path", since = "1.91.0")]
 impl cmp::PartialEq<str> for PathBuf {
     #[inline]
     fn eq(&self, other: &str) -> bool {
-        Path::eq(self, other)
+        self.as_path() == other
     }
 }
 
-#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "eq_str_for_path", since = "1.91.0")]
 impl cmp::PartialEq<PathBuf> for str {
     #[inline]
     fn eq(&self, other: &PathBuf) -> bool {
-        other == self
+        self == other.as_path()
     }
 }
 
-#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "eq_str_for_path", since = "1.91.0")]
 impl cmp::PartialEq<String> for PathBuf {
     #[inline]
     fn eq(&self, other: &String) -> bool {
-        **self == **other
+        self.as_path() == other.as_str()
     }
 }
 
-#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "eq_str_for_path", since = "1.91.0")]
 impl cmp::PartialEq<PathBuf> for String {
     #[inline]
     fn eq(&self, other: &PathBuf) -> bool {
-        other == self
+        self.as_str() == other.as_path()
     }
 }
 
@@ -2724,7 +2817,7 @@ impl Path {
     ///
     /// [`Path::file_stem`]: Path::file_stem
     ///
-    #[stable(feature = "path_file_prefix", since = "CURRENT_RUSTC_VERSION")]
+    #[stable(feature = "path_file_prefix", since = "1.91.0")]
     #[must_use]
     pub fn file_prefix(&self) -> Option<&OsStr> {
         self.file_name().map(split_file_at_dot).and_then(|(before, _after)| Some(before))
@@ -2755,6 +2848,94 @@ impl Path {
         self.file_name().map(rsplit_file_at_dot).and_then(|(before, after)| before.and(after))
     }
 
+    /// Checks whether the path ends in a trailing [separator](MAIN_SEPARATOR).
+    ///
+    /// This is generally done to ensure that a path is treated as a directory, not a file,
+    /// although it does not actually guarantee that such a path is a directory on the underlying
+    /// file system.
+    ///
+    /// Despite this behavior, two paths are still considered the same in Rust whether they have a
+    /// trailing separator or not.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(path_trailing_sep)]
+    /// use std::path::Path;
+    ///
+    /// assert!(Path::new("dir/").has_trailing_sep());
+    /// assert!(!Path::new("file.rs").has_trailing_sep());
+    /// ```
+    #[unstable(feature = "path_trailing_sep", issue = "142503")]
+    #[must_use]
+    #[inline]
+    pub fn has_trailing_sep(&self) -> bool {
+        self.as_os_str().as_encoded_bytes().last().copied().is_some_and(is_sep_byte)
+    }
+
+    /// Ensures that a path has a trailing [separator](MAIN_SEPARATOR),
+    /// allocating a [`PathBuf`] if necessary.
+    ///
+    /// The resulting path will return true for [`has_trailing_sep`](Self::has_trailing_sep).
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(path_trailing_sep)]
+    /// use std::ffi::OsStr;
+    /// use std::path::Path;
+    ///
+    /// assert_eq!(Path::new("dir//").with_trailing_sep().as_os_str(), OsStr::new("dir//"));
+    /// assert_eq!(Path::new("dir/").with_trailing_sep().as_os_str(), OsStr::new("dir/"));
+    /// assert!(!Path::new("dir").has_trailing_sep());
+    /// assert!(Path::new("dir").with_trailing_sep().has_trailing_sep());
+    /// ```
+    #[unstable(feature = "path_trailing_sep", issue = "142503")]
+    #[must_use]
+    #[inline]
+    pub fn with_trailing_sep(&self) -> Cow<'_, Path> {
+        if self.has_trailing_sep() { Cow::Borrowed(self) } else { Cow::Owned(self.join("")) }
+    }
+
+    /// Trims a trailing [separator](MAIN_SEPARATOR) from a path, if possible.
+    ///
+    /// The resulting path will return false for [`has_trailing_sep`](Self::has_trailing_sep) for
+    /// most paths.
+    ///
+    /// Some paths, like `/`, cannot be trimmed in this way.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(path_trailing_sep)]
+    /// use std::ffi::OsStr;
+    /// use std::path::Path;
+    ///
+    /// assert_eq!(Path::new("dir//").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
+    /// assert_eq!(Path::new("dir/").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
+    /// assert_eq!(Path::new("dir").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
+    /// assert_eq!(Path::new("/").trim_trailing_sep().as_os_str(), OsStr::new("/"));
+    /// assert_eq!(Path::new("//").trim_trailing_sep().as_os_str(), OsStr::new("//"));
+    /// ```
+    #[unstable(feature = "path_trailing_sep", issue = "142503")]
+    #[must_use]
+    #[inline]
+    pub fn trim_trailing_sep(&self) -> &Path {
+        if self.has_trailing_sep() && (!self.has_root() || self.parent().is_some()) {
+            let mut bytes = self.inner.as_encoded_bytes();
+            while let Some((last, init)) = bytes.split_last()
+                && is_sep_byte(*last)
+            {
+                bytes = init;
+            }
+
+            // SAFETY: Trimming trailing ASCII bytes will retain the validity of the string.
+            Path::new(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) })
+        } else {
+            self
+        }
+    }
+
     /// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
     ///
     /// If `path` is absolute, it replaces the current path.
@@ -2888,7 +3069,7 @@ impl Path {
     /// assert_eq!(path.with_added_extension("xz"), PathBuf::from("foo.tar.gz.xz"));
     /// assert_eq!(path.with_added_extension("").with_added_extension("txt"), PathBuf::from("foo.tar.gz.txt"));
     /// ```
-    #[stable(feature = "path_add_extension", since = "CURRENT_RUSTC_VERSION")]
+    #[stable(feature = "path_add_extension", since = "1.91.0")]
     pub fn with_added_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf {
         let mut new_path = self.to_path_buf();
         new_path.add_extension(extension);
@@ -2907,7 +3088,7 @@ impl Path {
     ///   `a/b` all have `a` and `b` as components, but `./a/b` starts with
     ///   an additional [`CurDir`] component.
     ///
-    /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
+    /// * Trailing separators are normalized away, so `/a/b` and `/a/b/` are equivalent.
     ///
     /// Note that no other normalization takes place; in particular, `a/c`
     /// and `a/b/../c` are distinct, to account for the possibility that `b`
@@ -3405,7 +3586,7 @@ impl PartialEq for Path {
     }
 }
 
-#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "eq_str_for_path", since = "1.91.0")]
 impl cmp::PartialEq<str> for Path {
     #[inline]
     fn eq(&self, other: &str) -> bool {
@@ -3414,7 +3595,7 @@ impl cmp::PartialEq<str> for Path {
     }
 }
 
-#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "eq_str_for_path", since = "1.91.0")]
 impl cmp::PartialEq<Path> for str {
     #[inline]
     fn eq(&self, other: &Path) -> bool {
@@ -3422,19 +3603,19 @@ impl cmp::PartialEq<Path> for str {
     }
 }
 
-#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "eq_str_for_path", since = "1.91.0")]
 impl cmp::PartialEq<String> for Path {
     #[inline]
     fn eq(&self, other: &String) -> bool {
-        self == &*other
+        self == other.as_str()
     }
 }
 
-#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
+#[stable(feature = "eq_str_for_path", since = "1.91.0")]
 impl cmp::PartialEq<Path> for String {
     #[inline]
     fn eq(&self, other: &Path) -> bool {
-        other == self
+        self.as_str() == other
     }
 }
 
@@ -3718,7 +3899,7 @@ impl Error for NormalizeError {}
 ///
 /// On POSIX platforms, the path is resolved using [POSIX semantics][posix-semantics],
 /// except that it stops short of resolving symlinks. This means it will keep `..`
-/// components and trailing slashes.
+/// components and trailing separators.
 ///
 /// On Windows, for verbatim paths, this will simply return the path as given. For other
 /// paths, this is currently equivalent to calling
diff --git a/library/std/src/sys/alloc/mod.rs b/library/std/src/sys/alloc/mod.rs
index 6d4b09494a3..2045b2fecc6 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 00000000000..c1fb6896a89
--- /dev/null
+++ b/library/std/src/sys/alloc/vexos.rs
@@ -0,0 +1,96 @@
+// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
+#![allow(static_mut_refs)]
+
+use crate::alloc::{GlobalAlloc, Layout, System};
+use crate::ptr;
+use crate::sync::atomic::{AtomicBool, Ordering};
+
+// Symbols for heap section boundaries defined in the target's linkerscript
+unsafe extern "C" {
+    static mut __heap_start: u8;
+    static mut __heap_end: u8;
+}
+
+static mut DLMALLOC: dlmalloc::Dlmalloc<Vexos> = 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) {
+            // 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::<u8>(),
+                    (&raw const __heap_end).offset_from_unsigned(&raw const __heap_start),
+                    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 {
+    #[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. 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()) }
+    }
+
+    #[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. 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()) }
+    }
+
+    #[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. 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()) }
+    }
+
+    #[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. 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/env_consts.rs b/library/std/src/sys/env_consts.rs
index 711ba0a5f8a..573f540483b 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/fs/mod.rs b/library/std/src/sys/fs/mod.rs
index 0276bf6e64c..64f5a6b36d3 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/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs
new file mode 100644
index 00000000000..f642e7cb074
--- /dev/null
+++ b/library/std/src/sys/fs/vexos.rs
@@ -0,0 +1,615 @@
+use crate::ffi::{OsString, c_char};
+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::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, FileTimes, canonicalize, link, readlink, remove_dir_all, rename, rmdir, symlink,
+    unlink,
+};
+
+/// VEXos file descriptor.
+///
+/// This stores an opaque pointer to a [FatFs file object structure] managed by VEXos
+/// representing an open file on disk.
+///
+/// [FatFs file object structure]: https://github.com/Xilinx/embeddedsw/blob/master/lib/sw_services/xilffs/src/include/ff.h?rgh-link-date=2025-09-23T20%3A03%3A43Z#L215
+///
+/// # Safety
+///
+/// Since this platform uses a pointer to to an internal filesystem structure with a lifetime
+/// associated with it (rather than a UNIX-style file descriptor table), care must be taken to
+/// ensure that the pointer held by `FileDesc` is valid for as long as it exists.
+#[derive(Debug)]
+struct FileDesc(*mut vex_sdk::FIL);
+
+// SAFETY: VEXos's FDs can be used on a thread other than the one they were created on.
+unsafe impl Send for FileDesc {}
+// SAFETY: We assume an environment without threads (i.e. no RTOS).
+// (If there were threads, it is possible that a mutex would be required.)
+unsafe impl Sync for FileDesc {}
+
+pub struct File {
+    fd: FileDesc,
+}
+
+#[derive(Clone)]
+pub enum FileAttr {
+    Dir,
+    File { size: u64 },
+}
+
+pub struct ReadDir(!);
+
+pub struct DirEntry {
+    path: PathBuf,
+}
+
+#[derive(Clone, Debug)]
+pub struct OpenOptions {
+    read: bool,
+    write: bool,
+    append: bool,
+    truncate: bool,
+    create: bool,
+    create_new: bool,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct FilePermissions {}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub struct FileType {
+    is_dir: bool,
+}
+
+impl FileAttr {
+    pub fn size(&self) -> u64 {
+        match self {
+            Self::File { size } => *size,
+            Self::Dir => 0,
+        }
+    }
+
+    pub fn perm(&self) -> FilePermissions {
+        FilePermissions {}
+    }
+
+    pub fn file_type(&self) -> FileType {
+        FileType { is_dir: matches!(self, FileAttr::Dir) }
+    }
+
+    pub fn modified(&self) -> io::Result<SystemTime> {
+        unsupported()
+    }
+
+    pub fn accessed(&self) -> io::Result<SystemTime> {
+        unsupported()
+    }
+
+    pub fn created(&self) -> io::Result<SystemTime> {
+        unsupported()
+    }
+}
+
+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
+    }
+
+    pub fn is_file(&self) -> bool {
+        !self.is_dir
+    }
+
+    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<DirEntry>;
+
+    fn next(&mut self) -> Option<io::Result<DirEntry>> {
+        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<FileAttr> {
+        stat(&self.path)
+    }
+
+    pub fn file_type(&self) -> io::Result<FileType> {
+        Ok(self.metadata()?.file_type())
+    }
+}
+
+impl OpenOptions {
+    pub fn new() -> OpenOptions {
+        OpenOptions {
+            read: false,
+            write: false,
+            append: false,
+            truncate: false,
+            create: 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.create = 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<File> {
+        run_path_with_cstr(path, &|path| {
+            // Enforce the invariants of `create_new`/`create`.
+            //
+            // Since VEXos doesn't have anything akin to POSIX's `oflags`, we need to enforce
+            // the requirements that `create_new` can't have an existing file and `!create`
+            // doesn't create a file ourselves.
+            if !opts.read && (opts.write || opts.append) && (opts.create_new || !opts.create) {
+                let status = unsafe { vex_sdk::vexFileStatus(path.as_ptr()) };
+
+                if opts.create_new && status != 0 {
+                    return Err(io::const_error!(io::ErrorKind::AlreadyExists, "file exists",));
+                } else if !opts.create && status == 0 {
+                    return Err(io::const_error!(
+                        io::ErrorKind::NotFound,
+                        "no such file or directory",
+                    ));
+                }
+            }
+
+            let file = match opts {
+                // read + write - unsupported
+                OpenOptions { read: true, write: true, .. } => {
+                    return Err(io::const_error!(
+                        io::ErrorKind::InvalidInput,
+                        "opening files with read and write access is unsupported on this target",
+                    ));
+                }
+
+                // read
+                OpenOptions {
+                    read: true,
+                    write: false,
+                    append: _,
+                    truncate: false,
+                    create: false,
+                    create_new: false,
+                } => unsafe { vex_sdk::vexFileOpen(path.as_ptr(), c"".as_ptr()) },
+
+                // append
+                OpenOptions {
+                    read: false,
+                    write: _,
+                    append: true,
+                    truncate: false,
+                    create: _,
+                    create_new: _,
+                } => unsafe { vex_sdk::vexFileOpenWrite(path.as_ptr()) },
+
+                // write
+                OpenOptions {
+                    read: false,
+                    write: true,
+                    append: false,
+                    truncate,
+                    create: _,
+                    create_new: _,
+                } => unsafe {
+                    if *truncate {
+                        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::const_error!(io::ErrorKind::InvalidInput, "invalid argument"));
+                }
+            };
+
+            if file.is_null() {
+                Err(io::const_error!(io::ErrorKind::NotFound, "could not open file"))
+            } else {
+                Ok(Self { fd: FileDesc(file) })
+            }
+        })
+    }
+
+    pub fn file_attr(&self) -> io::Result<FileAttr> {
+        // `vexFileSize` returns -1 upon error, so u64::try_from will fail on error.
+        if let Ok(size) = u64::try_from(unsafe {
+            // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+            vex_sdk::vexFileSize(self.fd.0)
+        }) {
+            Ok(FileAttr::File { size })
+        } else {
+            Err(io::const_error!(io::ErrorKind::InvalidData, "failed to get file size"))
+        }
+    }
+
+    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) -> Result<(), TryLockError> {
+        Err(TryLockError::Error(unsupported_err()))
+    }
+
+    pub fn try_lock_shared(&self) -> Result<(), TryLockError> {
+        Err(TryLockError::Error(unsupported_err()))
+    }
+
+    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<usize> {
+        let len = buf.len() as u32;
+        let buf_ptr = buf.as_mut_ptr();
+        let read = unsafe {
+            // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+            vex_sdk::vexFileRead(buf_ptr.cast::<c_char>(), 1, len, self.fd.0)
+        };
+
+        if read < 0 {
+            Err(io::const_error!(io::ErrorKind::Other, "could not read from file"))
+        } else {
+            Ok(read as usize)
+        }
+    }
+
+    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        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<usize> {
+        let len = buf.len() as u32;
+        let buf_ptr = buf.as_ptr();
+        let written = unsafe {
+            // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+            vex_sdk::vexFileWrite(buf_ptr.cast_mut().cast::<c_char>(), 1, len, self.fd.0)
+        };
+
+        if written < 0 {
+            Err(io::const_error!(io::ErrorKind::Other, "could not write to file"))
+        } else {
+            Ok(written as usize)
+        }
+    }
+
+    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        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 {
+            // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+            vex_sdk::vexFileSync(self.fd.0);
+        }
+        Ok(())
+    }
+
+    pub fn tell(&self) -> io::Result<u64> {
+        // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+        let position = unsafe { vex_sdk::vexFileTell(self.fd.0) };
+
+        position.try_into().map_err(|_| {
+            io::const_error!(io::ErrorKind::InvalidData, "failed to get current location in file")
+        })
+    }
+
+    pub fn size(&self) -> Option<io::Result<u64>> {
+        None
+    }
+
+    pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
+        const SEEK_SET: i32 = 0;
+        const SEEK_CUR: i32 = 1;
+        const SEEK_END: i32 = 2;
+
+        fn try_convert_offset<T: TryInto<u32>>(offset: T) -> io::Result<u32> {
+            offset.try_into().map_err(|_| {
+                io::const_error!(
+                    io::ErrorKind::InvalidInput,
+                    "cannot seek to an offset too large to fit in a 32 bit integer",
+                )
+            })
+        }
+
+        // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+        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.
+
+                    // Seek to the end of the file to get the end position in the open buffer.
+                    map_fresult(vex_sdk::vexFileSeek(self.fd.0, 0, SEEK_END))?;
+                    let end_position = self.tell()?;
+
+                    map_fresult(vex_sdk::vexFileSeek(
+                        self.fd.0,
+                        // NOTE: Files internally use a 32-bit representation for stream
+                        // position, so `end_position as i64` should never overflow.
+                        try_convert_offset(end_position 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<File> {
+        unsupported()
+    }
+
+    pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
+        unsupported()
+    }
+
+    pub fn set_times(&self, _times: FileTimes) -> 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<ReadDir> {
+    // 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<bool> {
+    run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0))
+}
+
+pub fn stat(p: &Path) -> io::Result<FileAttr> {
+    // `vexFileStatus` returns 3 if the given path is a directory, 1 if the path is a
+    // file, or 0 if no such path exists.
+    const FILE_STATUS_DIR: u32 = 3;
+
+    run_path_with_cstr(p, &|c_path| {
+        let file_type = unsafe { vex_sdk::vexFileStatus(c_path.as_ptr()) };
+
+        // We can't get the size if its a directory because we cant open it as a file
+        if file_type == FILE_STATUS_DIR {
+            Ok(FileAttr::Dir)
+        } else {
+            let mut opts = OpenOptions::new();
+            opts.read(true);
+            let file = File::open(p, &opts)?;
+            file.file_attr()
+        }
+    })
+}
+
+pub fn lstat(p: &Path) -> io::Result<FileAttr> {
+    // Symlinks aren't supported in this filesystem
+    stat(p)
+}
+
+// Cannot use `copy` from `common` here, since `File::set_permissions` is unsupported on this target.
+pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
+    use crate::fs::File;
+
+    // NOTE: If `from` is a directory, this call should fail due to vexFileOpen* returning null.
+    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 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::const_error!(
+            io::ErrorKind::Uncategorized,
+            "internal function reported an unrecoverable hard error",
+        )),
+        vex_sdk::FRESULT::FR_INT_ERR => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "internal error in filesystem runtime",
+        )),
+        vex_sdk::FRESULT::FR_NOT_READY => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "the storage device could not be prepared to work",
+        )),
+        vex_sdk::FRESULT::FR_NO_FILE => Err(io::const_error!(
+            io::ErrorKind::NotFound,
+            "could not find the file in the directory"
+        )),
+        vex_sdk::FRESULT::FR_NO_PATH => Err(io::const_error!(
+            io::ErrorKind::NotFound,
+            "a directory in the path name could not be found",
+        )),
+        vex_sdk::FRESULT::FR_INVALID_NAME => Err(io::const_error!(
+            io::ErrorKind::InvalidInput,
+            "the given string is invalid as a path name",
+        )),
+        vex_sdk::FRESULT::FR_DENIED => Err(io::const_error!(
+            io::ErrorKind::PermissionDenied,
+            "the required access for this operation was denied",
+        )),
+        vex_sdk::FRESULT::FR_EXIST => Err(io::const_error!(
+            io::ErrorKind::AlreadyExists,
+            "an object with the same name already exists in the directory",
+        )),
+        vex_sdk::FRESULT::FR_INVALID_OBJECT => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "invalid or null file/directory object",
+        )),
+        vex_sdk::FRESULT::FR_WRITE_PROTECTED => Err(io::const_error!(
+            io::ErrorKind::PermissionDenied,
+            "a write operation was performed on write-protected media",
+        )),
+        vex_sdk::FRESULT::FR_INVALID_DRIVE => Err(io::const_error!(
+            io::ErrorKind::InvalidInput,
+            "an invalid drive number was specified in the path name",
+        )),
+        vex_sdk::FRESULT::FR_NOT_ENABLED => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "work area for the logical drive has not been registered",
+        )),
+        vex_sdk::FRESULT::FR_NO_FILESYSTEM => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "valid FAT volume could not be found on the drive",
+        )),
+        vex_sdk::FRESULT::FR_MKFS_ABORTED => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "failed to create filesystem volume"
+        )),
+        vex_sdk::FRESULT::FR_TIMEOUT => Err(io::const_error!(
+            io::ErrorKind::TimedOut,
+            "the function was canceled due to a timeout of thread-safe control",
+        )),
+        vex_sdk::FRESULT::FR_LOCKED => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "the operation to the object was rejected by file sharing control",
+        )),
+        vex_sdk::FRESULT::FR_NOT_ENOUGH_CORE => {
+            Err(io::const_error!(io::ErrorKind::OutOfMemory, "not enough memory for the operation"))
+        }
+        vex_sdk::FRESULT::FR_TOO_MANY_OPEN_FILES => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "maximum number of open files has been reached",
+        )),
+        vex_sdk::FRESULT::FR_INVALID_PARAMETER => {
+            Err(io::const_error!(io::ErrorKind::InvalidInput, "a given parameter was invalid"))
+        }
+        _ => unreachable!(), // C-style enum
+    }
+}
diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs
index 8216f8d2fd5..a191576d93b 100644
--- a/library/std/src/sys/net/connection/socket/unix.rs
+++ b/library/std/src/sys/net/connection/socket/unix.rs
@@ -485,14 +485,15 @@ impl Socket {
 
     // bionic libc makes no use of this flag
     #[cfg(target_os = "linux")]
-    pub fn set_deferaccept(&self, accept: u32) -> io::Result<()> {
-        setsockopt(self, libc::IPPROTO_TCP, libc::TCP_DEFER_ACCEPT, accept as c_int)
+    pub fn set_deferaccept(&self, accept: Duration) -> io::Result<()> {
+        let val = cmp::min(accept.as_secs(), c_int::MAX as u64) as c_int;
+        setsockopt(self, libc::IPPROTO_TCP, libc::TCP_DEFER_ACCEPT, val)
     }
 
     #[cfg(target_os = "linux")]
-    pub fn deferaccept(&self) -> io::Result<u32> {
+    pub fn deferaccept(&self) -> io::Result<Duration> {
         let raw: c_int = getsockopt(self, libc::IPPROTO_TCP, libc::TCP_DEFER_ACCEPT)?;
-        Ok(raw as u32)
+        Ok(Duration::from_secs(raw as _))
     }
 
     #[cfg(any(target_os = "freebsd", target_os = "netbsd"))]
diff --git a/library/std/src/sys/net/connection/socket/windows.rs b/library/std/src/sys/net/connection/socket/windows.rs
index b71d8b1357b..5b6f4cedf1b 100644
--- a/library/std/src/sys/net/connection/socket/windows.rs
+++ b/library/std/src/sys/net/connection/socket/windows.rs
@@ -8,9 +8,8 @@ use crate::net::{Shutdown, SocketAddr};
 use crate::os::windows::io::{
     AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket,
 };
-use crate::sync::atomic::Atomic;
-use crate::sync::atomic::Ordering::{AcqRel, Relaxed};
 use crate::sys::c;
+use crate::sys::pal::winsock::last_error;
 use crate::sys_common::{AsInner, FromInner, IntoInner};
 use crate::time::Duration;
 use crate::{cmp, mem, ptr, sys};
@@ -112,84 +111,11 @@ pub(super) mod netc {
     }
 }
 
+pub use crate::sys::pal::winsock::{cleanup, cvt, cvt_gai, cvt_r, startup as init};
+
 #[expect(missing_debug_implementations)]
 pub struct Socket(OwnedSocket);
 
-static WSA_INITIALIZED: Atomic<bool> = Atomic::<bool>::new(false);
-
-/// Checks whether the Windows socket interface has been started already, and
-/// if not, starts it.
-#[inline]
-pub fn init() {
-    if !WSA_INITIALIZED.load(Relaxed) {
-        wsa_startup();
-    }
-}
-
-#[cold]
-fn wsa_startup() {
-    unsafe {
-        let mut data: c::WSADATA = mem::zeroed();
-        let ret = c::WSAStartup(
-            0x202, // version 2.2
-            &mut data,
-        );
-        assert_eq!(ret, 0);
-        if WSA_INITIALIZED.swap(true, AcqRel) {
-            // If another thread raced with us and called WSAStartup first then call
-            // WSACleanup so it's as though WSAStartup was only called once.
-            c::WSACleanup();
-        }
-    }
-}
-
-pub fn cleanup() {
-    // We don't need to call WSACleanup here because exiting the process will cause
-    // the OS to clean everything for us, which is faster than doing it manually.
-    // See #141799.
-}
-
-/// Returns the last error from the Windows socket interface.
-fn last_error() -> io::Error {
-    io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() })
-}
-
-#[doc(hidden)]
-pub trait IsMinusOne {
-    fn is_minus_one(&self) -> bool;
-}
-
-macro_rules! impl_is_minus_one {
-    ($($t:ident)*) => ($(impl IsMinusOne for $t {
-        fn is_minus_one(&self) -> bool {
-            *self == -1
-        }
-    })*)
-}
-
-impl_is_minus_one! { i8 i16 i32 i64 isize }
-
-/// Checks if the signed integer is the Windows constant `SOCKET_ERROR` (-1)
-/// and if so, returns the last error from the Windows socket interface. This
-/// function must be called before another call to the socket API is made.
-pub fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
-    if t.is_minus_one() { Err(last_error()) } else { Ok(t) }
-}
-
-/// A variant of `cvt` for `getaddrinfo` which return 0 for a success.
-pub fn cvt_gai(err: c_int) -> io::Result<()> {
-    if err == 0 { Ok(()) } else { Err(last_error()) }
-}
-
-/// Just to provide the same interface as sys/pal/unix/net.rs
-pub fn cvt_r<T, F>(mut f: F) -> io::Result<T>
-where
-    T: IsMinusOne,
-    F: FnMut() -> T,
-{
-    cvt(f())
-}
-
 impl Socket {
     pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result<Socket> {
         let family = match *addr {
diff --git a/library/std/src/sys/net/hostname/mod.rs b/library/std/src/sys/net/hostname/mod.rs
new file mode 100644
index 00000000000..8ffe4894d71
--- /dev/null
+++ b/library/std/src/sys/net/hostname/mod.rs
@@ -0,0 +1,14 @@
+cfg_select! {
+    all(target_family = "unix", not(target_os = "espidf")) => {
+        mod unix;
+        pub use unix::hostname;
+    }
+    target_os = "windows" => {
+        mod windows;
+        pub use windows::hostname;
+    }
+    _ => {
+        mod unsupported;
+        pub use unsupported::hostname;
+    }
+}
diff --git a/library/std/src/sys/net/hostname/unix.rs b/library/std/src/sys/net/hostname/unix.rs
new file mode 100644
index 00000000000..bc6fa82a38f
--- /dev/null
+++ b/library/std/src/sys/net/hostname/unix.rs
@@ -0,0 +1,62 @@
+use crate::ffi::OsString;
+use crate::io;
+use crate::os::unix::ffi::OsStringExt;
+use crate::sys::pal::os::errno;
+
+pub fn hostname() -> io::Result<OsString> {
+    // Query the system for the maximum host name length.
+    let host_name_max = match unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) } {
+        // If this fails (possibly because there is no maximum length), then
+        // assume a maximum length of _POSIX_HOST_NAME_MAX (255).
+        -1 => 255,
+        max => max as usize,
+    };
+
+    // Reserve space for the nul terminator too.
+    let mut buf = Vec::<u8>::try_with_capacity(host_name_max + 1)?;
+    loop {
+        // SAFETY: `buf.capacity()` bytes of `buf` are writable.
+        let r = unsafe { libc::gethostname(buf.as_mut_ptr().cast(), buf.capacity()) };
+        match (r != 0).then(errno) {
+            None => {
+                // Unfortunately, the UNIX specification says that the name will
+                // be truncated if it does not fit in the buffer, without returning
+                // an error. As additionally, the truncated name may still be null-
+                // terminated, there is no reliable way to  detect truncation.
+                // Fortunately, most platforms ignore what the specification says
+                // and return an error (mostly ENAMETOOLONG). Should that not be
+                // the case, the following detects truncation if the null-terminator
+                // was omitted. Note that this check does not impact performance at
+                // all as we need to find the length of the string anyways.
+                //
+                // Use `strnlen` as it does not place an initialization requirement
+                // on the bytes after the nul terminator.
+                //
+                // SAFETY: `buf.capacity()` bytes of `buf` are accessible, and are
+                // initialized up to and including a possible nul terminator.
+                let len = unsafe { libc::strnlen(buf.as_ptr().cast(), buf.capacity()) };
+                if len < buf.capacity() {
+                    // If the string is nul-terminated, we assume that is has not
+                    // been truncated, as the capacity *should be* enough to hold
+                    // `HOST_NAME_MAX` bytes.
+                    // SAFETY: `len + 1` bytes have been initialized (we exclude
+                    // the nul terminator from the string).
+                    unsafe { buf.set_len(len) };
+                    return Ok(OsString::from_vec(buf));
+                }
+            }
+            // As `buf.capacity()` is always less than or equal to `isize::MAX`
+            // (Rust allocations cannot exceed that limit), the only way `EINVAL`
+            // can be returned is if the system uses `EINVAL` to report that the
+            // name does not fit in the provided buffer. In that case (or in the
+            // case of `ENAMETOOLONG`), resize the buffer and try again.
+            Some(libc::EINVAL | libc::ENAMETOOLONG) => {}
+            // Other error codes (e.g. EPERM) have nothing to do with the buffer
+            // size and should be returned to the user.
+            Some(err) => return Err(io::Error::from_raw_os_error(err)),
+        }
+
+        // Resize the buffer (according to `Vec`'s resizing rules) and try again.
+        buf.try_reserve(buf.capacity() + 1)?;
+    }
+}
diff --git a/library/std/src/sys/net/hostname/unsupported.rs b/library/std/src/sys/net/hostname/unsupported.rs
new file mode 100644
index 00000000000..d868f68f32d
--- /dev/null
+++ b/library/std/src/sys/net/hostname/unsupported.rs
@@ -0,0 +1,6 @@
+use crate::ffi::OsString;
+use crate::io::{Error, Result};
+
+pub fn hostname() -> Result<OsString> {
+    Err(Error::UNSUPPORTED_PLATFORM)
+}
diff --git a/library/std/src/sys/net/hostname/windows.rs b/library/std/src/sys/net/hostname/windows.rs
new file mode 100644
index 00000000000..24eed100f32
--- /dev/null
+++ b/library/std/src/sys/net/hostname/windows.rs
@@ -0,0 +1,24 @@
+use crate::ffi::OsString;
+use crate::io::Result;
+use crate::mem::MaybeUninit;
+use crate::os::windows::ffi::OsStringExt;
+use crate::sys::pal::c;
+use crate::sys::pal::winsock::{self, cvt};
+
+pub fn hostname() -> Result<OsString> {
+    winsock::startup();
+
+    // The documentation of GetHostNameW says that a buffer size of 256 is
+    // always enough.
+    let mut buffer = [const { MaybeUninit::<u16>::uninit() }; 256];
+    // SAFETY: these parameters specify a valid, writable region of memory.
+    cvt(unsafe { c::GetHostNameW(buffer.as_mut_ptr().cast(), buffer.len() as i32) })?;
+    // Use `lstrlenW` here as it does not require the bytes after the nul
+    // terminator to be initialized.
+    // SAFETY: if `GetHostNameW` returns successfully, the name is nul-terminated.
+    let len = unsafe { c::lstrlenW(buffer.as_ptr().cast()) };
+    // SAFETY: the length of the name is `len`, hence `len` bytes have been
+    //         initialized by `GetHostNameW`.
+    let name = unsafe { buffer[..len as usize].assume_init_ref() };
+    Ok(OsString::from_wide(name))
+}
diff --git a/library/std/src/sys/net/mod.rs b/library/std/src/sys/net/mod.rs
index dffc4ea7f81..bfe5cf53128 100644
--- a/library/std/src/sys/net/mod.rs
+++ b/library/std/src/sys/net/mod.rs
@@ -2,3 +2,6 @@
 /// `UdpSocket` as well as related functionality like DNS resolving.
 mod connection;
 pub use connection::*;
+
+mod hostname;
+pub use hostname::hostname;
diff --git a/library/std/src/sys/pal/hermit/time.rs b/library/std/src/sys/pal/hermit/time.rs
index f76a5f96c87..bd6fd5a3de4 100644
--- a/library/std/src/sys/pal/hermit/time.rs
+++ b/library/std/src/sys/pal/hermit/time.rs
@@ -26,15 +26,22 @@ impl Timespec {
     }
 
     fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
+        fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 {
+            debug_assert!(a >= b);
+            a.wrapping_sub(b).cast_unsigned()
+        }
+
         if self >= other {
+            // Logic here is identical to Unix version of `Timestamp::sub_timespec`,
+            // check comments there why operations do not overflow.
             Ok(if self.t.tv_nsec >= other.t.tv_nsec {
                 Duration::new(
-                    (self.t.tv_sec - other.t.tv_sec) as u64,
+                    sub_ge_to_unsigned(self.t.tv_sec, other.t.tv_sec),
                     (self.t.tv_nsec - other.t.tv_nsec) as u32,
                 )
             } else {
                 Duration::new(
-                    (self.t.tv_sec - 1 - other.t.tv_sec) as u64,
+                    sub_ge_to_unsigned(self.t.tv_sec - 1, other.t.tv_sec),
                     (self.t.tv_nsec + NSEC_PER_SEC - other.t.tv_nsec) as u32,
                 )
             })
diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs
index 513121c6d30..dd5e83ee570 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/uefi/helpers.rs b/library/std/src/sys/pal/uefi/helpers.rs
index b50574de937..c0d69c3e002 100644
--- a/library/std/src/sys/pal/uefi/helpers.rs
+++ b/library/std/src/sys/pal/uefi/helpers.rs
@@ -92,6 +92,9 @@ pub(crate) fn locate_handles(mut guid: Guid) -> io::Result<Vec<NonNull<crate::ff
 ///
 /// Queries a handle to determine if it supports a specified protocol. If the protocol is
 /// supported by the handle, it opens the protocol on behalf of the calling agent.
+///
+/// The protocol is opened with the attribute GET_PROTOCOL, which means the caller is not required
+/// to close the protocol interface with `EFI_BOOT_SERVICES.CloseProtocol()`
 pub(crate) fn open_protocol<T>(
     handle: NonNull<crate::ffi::c_void>,
     mut protocol_guid: Guid,
@@ -473,6 +476,7 @@ impl<'a> crate::fmt::Debug for DevicePathNode<'a> {
     }
 }
 
+/// Protocols installed by Rust side on a handle.
 pub(crate) struct OwnedProtocol<T> {
     guid: r_efi::efi::Guid,
     handle: NonNull<crate::ffi::c_void>,
diff --git a/library/std/src/sys/pal/unix/os.rs b/library/std/src/sys/pal/unix/os.rs
index f0b6068e06c..7c9f3b7992f 100644
--- a/library/std/src/sys/pal/unix/os.rs
+++ b/library/std/src/sys/pal/unix/os.rs
@@ -16,7 +16,7 @@ use crate::{fmt, io, iter, mem, ptr, slice, str};
 
 const TMPBUF_SZ: usize = 128;
 
-const PATH_SEPARATOR: u8 = if cfg!(target_os = "redox") { b';' } else { b':' };
+const PATH_SEPARATOR: u8 = b':';
 
 unsafe extern "C" {
     #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))]
diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs
index 0d2100d66bc..28b05d8a68a 100644
--- a/library/std/src/sys/pal/unix/stack_overflow.rs
+++ b/library/std/src/sys/pal/unix/stack_overflow.rs
@@ -72,7 +72,7 @@ mod imp {
     use crate::sync::OnceLock;
     use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering};
     use crate::sys::pal::unix::os;
-    use crate::{io, mem, panic, ptr};
+    use crate::{io, mem, ptr};
 
     // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages
     // (unmapped pages) at the end of every thread's stack, so if a thread ends
@@ -148,6 +148,13 @@ mod imp {
 
         let mut guard_page_range = unsafe { install_main_guard() };
 
+        // Even for panic=immediate-abort, installing the guard pages is important for soundness.
+        // That said, we do not care about giving nice stackoverflow messages via our custom
+        // signal handler, just exit early and let the user enjoy the segfault.
+        if cfg!(panic = "immediate-abort") {
+            return;
+        }
+
         // SAFETY: assuming all platforms define struct sigaction as "zero-initializable"
         let mut action: sigaction = unsafe { mem::zeroed() };
         for &signal in &[SIGSEGV, SIGBUS] {
@@ -179,6 +186,9 @@ mod imp {
     /// Must be called only once
     #[forbid(unsafe_op_in_unsafe_fn)]
     pub unsafe fn cleanup() {
+        if cfg!(panic = "immediate-abort") {
+            return;
+        }
         // FIXME: I probably cause more bugs than I'm worth!
         // see https://github.com/rust-lang/rust/issues/111272
         unsafe { drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)) };
@@ -230,7 +240,7 @@ mod imp {
     /// Mutates the alternate signal stack
     #[forbid(unsafe_op_in_unsafe_fn)]
     pub unsafe fn make_handler(main_thread: bool, thread_name: Option<Box<str>>) -> Handler {
-        if !NEED_ALTSTACK.load(Ordering::Acquire) {
+        if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) {
             return Handler::null();
         }
 
diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs
index bd7f74fea6a..c207f41cad4 100644
--- a/library/std/src/sys/pal/unix/time.rs
+++ b/library/std/src/sys/pal/unix/time.rs
@@ -134,28 +134,25 @@ impl Timespec {
     }
 
     pub fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
+        // When a >= b, the difference fits in u64.
+        fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 {
+            debug_assert!(a >= b);
+            a.wrapping_sub(b).cast_unsigned()
+        }
+
         if self >= other {
-            // NOTE(eddyb) two aspects of this `if`-`else` are required for LLVM
-            // to optimize it into a branchless form (see also #75545):
-            //
-            // 1. `self.tv_sec - other.tv_sec` shows up as a common expression
-            //    in both branches, i.e. the `else` must have its `- 1`
-            //    subtraction after the common one, not interleaved with it
-            //    (it used to be `self.tv_sec - 1 - other.tv_sec`)
-            //
-            // 2. the `Duration::new` call (or any other additional complexity)
-            //    is outside of the `if`-`else`, not duplicated in both branches
-            //
-            // Ideally this code could be rearranged such that it more
-            // directly expresses the lower-cost behavior we want from it.
             let (secs, nsec) = if self.tv_nsec.as_inner() >= other.tv_nsec.as_inner() {
                 (
-                    (self.tv_sec - other.tv_sec) as u64,
+                    sub_ge_to_unsigned(self.tv_sec, other.tv_sec),
                     self.tv_nsec.as_inner() - other.tv_nsec.as_inner(),
                 )
             } else {
+                // Following sequence of assertions explain why `self.tv_sec - 1` does not underflow.
+                debug_assert!(self.tv_nsec < other.tv_nsec);
+                debug_assert!(self.tv_sec > other.tv_sec);
+                debug_assert!(self.tv_sec > i64::MIN);
                 (
-                    (self.tv_sec - other.tv_sec - 1) as u64,
+                    sub_ge_to_unsigned(self.tv_sec - 1, other.tv_sec),
                     self.tv_nsec.as_inner() + (NSEC_PER_SEC as u32) - other.tv_nsec.as_inner(),
                 )
             };
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 00000000000..61a34b0f68a
--- /dev/null
+++ b/library/std/src/sys/pal/vexos/mod.rs
@@ -0,0 +1,80 @@
+#[path = "../unsupported/os.rs"]
+pub mod os;
+#[path = "../unsupported/pipe.rs"]
+pub mod pipe;
+pub mod time;
+
+#[expect(dead_code)]
+#[path = "../unsupported/common.rs"]
+mod unsupported_common;
+
+pub use unsupported_common::{
+    decode_error_kind, init, is_interrupted, unsupported, unsupported_err,
+};
+
+use crate::arch::global_asm;
+use crate::ptr;
+use crate::sys::stdio;
+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))]
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn _start() -> ! {
+    unsafe extern "C" {
+        static mut __bss_start: u8;
+        static mut __bss_end: u8;
+
+        fn main() -> i32;
+    }
+
+    // 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_unsigned(&raw mut __bss_start),
+    );
+
+    main();
+
+    cleanup();
+    abort_internal()
+}
+
+// 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() {
+    let exit_time = Instant::now();
+    const FLUSH_TIMEOUT: Duration = Duration::from_millis(15);
+
+    // 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;
+        }
+    }
+}
+
+pub fn abort_internal() -> ! {
+    unsafe {
+        vex_sdk::vexSystemExitRequest();
+
+        loop {
+            vex_sdk::vexTasksRun();
+        }
+    }
+}
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 00000000000..f95d96cd27a
--- /dev/null
+++ b/library/std/src/sys/pal/vexos/time.rs
@@ -0,0 +1,28 @@
+use crate::time::Duration;
+
+#[expect(dead_code)]
+#[path = "../unsupported/time.rs"]
+mod unsupported_time;
+pub use unsupported_time::{SystemTime, UNIX_EPOCH};
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
+pub struct Instant(Duration);
+
+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<Duration> {
+        self.0.checked_sub(other.0)
+    }
+
+    pub fn checked_add_duration(&self, other: &Duration) -> Option<Instant> {
+        Some(Instant(self.0.checked_add(*other)?))
+    }
+
+    pub fn checked_sub_duration(&self, other: &Duration) -> Option<Instant> {
+        Some(Instant(self.0.checked_sub(*other)?))
+    }
+}
diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt
index abc1c19827f..9009aa09f48 100644
--- a/library/std/src/sys/pal/windows/c/bindings.txt
+++ b/library/std/src/sys/pal/windows/c/bindings.txt
@@ -2170,6 +2170,7 @@ GetFileType
 GETFINALPATHNAMEBYHANDLE_FLAGS
 GetFinalPathNameByHandleW
 GetFullPathNameW
+GetHostNameW
 GetLastError
 GetModuleFileNameW
 GetModuleHandleA
@@ -2270,6 +2271,7 @@ LPPROGRESS_ROUTINE
 LPPROGRESS_ROUTINE_CALLBACK_REASON
 LPTHREAD_START_ROUTINE
 LPWSAOVERLAPPED_COMPLETION_ROUTINE
+lstrlenW
 M128A
 MAX_PATH
 MAXIMUM_REPARSE_DATA_BUFFER_SIZE
diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs
index 989a1246650..98f277b3378 100644
--- a/library/std/src/sys/pal/windows/c/windows_sys.rs
+++ b/library/std/src/sys/pal/windows/c/windows_sys.rs
@@ -49,6 +49,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE,
 windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
 windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
 windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32);
+windows_targets::link!("ws2_32.dll" "system" fn GetHostNameW(name : PWSTR, namelen : i32) -> i32);
 windows_targets::link!("kernel32.dll" "system" fn GetLastError() -> WIN32_ERROR);
 windows_targets::link!("kernel32.dll" "system" fn GetModuleFileNameW(hmodule : HMODULE, lpfilename : PWSTR, nsize : u32) -> u32);
 windows_targets::link!("kernel32.dll" "system" fn GetModuleHandleA(lpmodulename : PCSTR) -> HMODULE);
@@ -134,6 +135,7 @@ windows_targets::link!("ws2_32.dll" "system" fn getsockname(s : SOCKET, name : *
 windows_targets::link!("ws2_32.dll" "system" fn getsockopt(s : SOCKET, level : i32, optname : i32, optval : PSTR, optlen : *mut i32) -> i32);
 windows_targets::link!("ws2_32.dll" "system" fn ioctlsocket(s : SOCKET, cmd : i32, argp : *mut u32) -> i32);
 windows_targets::link!("ws2_32.dll" "system" fn listen(s : SOCKET, backlog : i32) -> i32);
+windows_targets::link!("kernel32.dll" "system" fn lstrlenW(lpstring : PCWSTR) -> i32);
 windows_targets::link!("ws2_32.dll" "system" fn recv(s : SOCKET, buf : PSTR, len : i32, flags : SEND_RECV_FLAGS) -> i32);
 windows_targets::link!("ws2_32.dll" "system" fn recvfrom(s : SOCKET, buf : PSTR, len : i32, flags : i32, from : *mut SOCKADDR, fromlen : *mut i32) -> i32);
 windows_targets::link!("ws2_32.dll" "system" fn select(nfds : i32, readfds : *mut FD_SET, writefds : *mut FD_SET, exceptfds : *mut FD_SET, timeout : *const TIMEVAL) -> i32);
diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs
index 3357946b8f7..18ab3498267 100644
--- a/library/std/src/sys/pal/windows/mod.rs
+++ b/library/std/src/sys/pal/windows/mod.rs
@@ -22,7 +22,8 @@ pub mod os;
 pub mod pipe;
 pub mod time;
 cfg_select! {
-    not(target_vendor = "uwp") => {
+    // We don't care about printing nice error messages for panic=immediate-abort
+    all(not(target_vendor = "uwp"), not(panic = "immediate-abort")) => {
         pub mod stack_overflow;
     }
     _ => {
@@ -30,6 +31,7 @@ cfg_select! {
         pub use self::stack_overflow_uwp as stack_overflow;
     }
 }
+pub mod winsock;
 
 /// Map a [`Result<T, WinError>`] to [`io::Result<T>`](crate::io::Result<T>).
 pub trait IoResult<T> {
diff --git a/library/std/src/sys/pal/windows/winsock.rs b/library/std/src/sys/pal/windows/winsock.rs
new file mode 100644
index 00000000000..b110a43ef3a
--- /dev/null
+++ b/library/std/src/sys/pal/windows/winsock.rs
@@ -0,0 +1,80 @@
+use super::c;
+use crate::ffi::c_int;
+use crate::sync::atomic::Atomic;
+use crate::sync::atomic::Ordering::{AcqRel, Relaxed};
+use crate::{io, mem};
+
+static WSA_STARTED: Atomic<bool> = Atomic::<bool>::new(false);
+
+/// Checks whether the Windows socket interface has been started already, and
+/// if not, starts it.
+#[inline]
+pub fn startup() {
+    if !WSA_STARTED.load(Relaxed) {
+        wsa_startup();
+    }
+}
+
+#[cold]
+fn wsa_startup() {
+    unsafe {
+        let mut data: c::WSADATA = mem::zeroed();
+        let ret = c::WSAStartup(
+            0x202, // version 2.2
+            &mut data,
+        );
+        assert_eq!(ret, 0);
+        if WSA_STARTED.swap(true, AcqRel) {
+            // If another thread raced with us and called WSAStartup first then call
+            // WSACleanup so it's as though WSAStartup was only called once.
+            c::WSACleanup();
+        }
+    }
+}
+
+pub fn cleanup() {
+    // We don't need to call WSACleanup here because exiting the process will cause
+    // the OS to clean everything for us, which is faster than doing it manually.
+    // See #141799.
+}
+
+/// Returns the last error from the Windows socket interface.
+pub fn last_error() -> io::Error {
+    io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() })
+}
+
+#[doc(hidden)]
+pub trait IsMinusOne {
+    fn is_minus_one(&self) -> bool;
+}
+
+macro_rules! impl_is_minus_one {
+    ($($t:ident)*) => ($(impl IsMinusOne for $t {
+        fn is_minus_one(&self) -> bool {
+            *self == -1
+        }
+    })*)
+}
+
+impl_is_minus_one! { i8 i16 i32 i64 isize }
+
+/// Checks if the signed integer is the Windows constant `SOCKET_ERROR` (-1)
+/// and if so, returns the last error from the Windows socket interface. This
+/// function must be called before another call to the socket API is made.
+pub fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
+    if t.is_minus_one() { Err(last_error()) } else { Ok(t) }
+}
+
+/// A variant of `cvt` for `getaddrinfo` which return 0 for a success.
+pub fn cvt_gai(err: c_int) -> io::Result<()> {
+    if err == 0 { Ok(()) } else { Err(last_error()) }
+}
+
+/// Just to provide the same interface as sys/pal/unix/net.rs
+pub fn cvt_r<T, F>(mut f: F) -> io::Result<T>
+where
+    T: IsMinusOne,
+    F: FnMut() -> T,
+{
+    cvt(f())
+}
diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs
index 1e0eec07b50..3c5a4c82a9f 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/stdio/mod.rs b/library/std/src/sys/stdio/mod.rs
index 7436e4d9de4..404ac877926 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/stdio/vexos.rs b/library/std/src/sys/stdio/vexos.rs
new file mode 100644
index 00000000000..9a391feb7a8
--- /dev/null
+++ b/library/std/src/sys/stdio/vexos.rs
@@ -0,0 +1,100 @@
+use crate::io;
+
+pub struct Stdin;
+pub struct Stdout;
+pub type Stderr = Stdout;
+
+pub const STDIO_CHANNEL: u32 = 1;
+
+impl Stdin {
+    pub const fn new() -> Stdin {
+        Stdin
+    }
+}
+
+impl io::Read for Stdin {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        let mut count = 0;
+
+        for out_byte in buf.iter_mut() {
+            let byte = unsafe { vex_sdk::vexSerialReadChar(STDIO_CHANNEL) };
+            if byte < 0 {
+                break;
+            }
+
+            *out_byte = byte as u8;
+            count += 1;
+        }
+
+        Ok(count)
+    }
+}
+
+impl Stdout {
+    pub const fn new() -> Stdout {
+        Stdout
+    }
+}
+
+impl io::Write for Stdout {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        let mut written = 0;
+
+        // 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();
+            }
+
+            let count: usize = unsafe {
+                vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, chunk.as_ptr(), chunk.len() as u32)
+            }
+            .try_into()
+            .map_err(|_| {
+                io::const_error!(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(written)
+    }
+
+    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();
+            }
+        }
+
+        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<impl io::Write> {
+    Some(Stdout::new())
+}
diff --git a/library/std/src/sys/thread/mod.rs b/library/std/src/sys/thread/mod.rs
index 6bb7fc1a20e..3bd83dd760a 100644
--- a/library/std/src/sys/thread/mod.rs
+++ b/library/std/src/sys/thread/mod.rs
@@ -81,6 +81,13 @@ cfg_select! {
         ))]
         pub use unsupported::set_name;
     }
+    target_os = "vexos" => {
+        mod vexos;
+        pub use vexos::{sleep, yield_now};
+        #[expect(dead_code)]
+        mod unsupported;
+        pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, DEFAULT_MIN_STACK_SIZE};
+    }
     all(target_os = "wasi", target_env = "p1") => {
         mod wasip1;
         pub use wasip1::{DEFAULT_MIN_STACK_SIZE, sleep, yield_now};
diff --git a/library/std/src/sys/thread/vexos.rs b/library/std/src/sys/thread/vexos.rs
new file mode 100644
index 00000000000..d917dde4d0b
--- /dev/null
+++ b/library/std/src/sys/thread/vexos.rs
@@ -0,0 +1,17 @@
+use crate::time::{Duration, Instant};
+
+pub fn yield_now() {
+    unsafe {
+        vex_sdk::vexTasksRun();
+    }
+}
+
+pub fn sleep(dur: Duration) {
+    let start = Instant::now();
+
+    while start.elapsed() < dur {
+        unsafe {
+            vex_sdk::vexTasksRun();
+        }
+    }
+}
diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs
index cff74857c47..f7f051b1add 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};
@@ -41,7 +42,7 @@ cfg_select! {
     }
     _ => {
         mod os;
-        pub use os::{Storage, thread_local_inner};
+        pub use os::{Storage, thread_local_inner, value_align};
         pub(crate) use os::{LocalPointer, local_pointer};
     }
 }
@@ -98,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
diff --git a/library/std/src/sys/thread_local/native/eager.rs b/library/std/src/sys/thread_local/native/eager.rs
index fd48c4f7202..23abad645c1 100644
--- a/library/std/src/sys/thread_local/native/eager.rs
+++ b/library/std/src/sys/thread_local/native/eager.rs
@@ -10,9 +10,11 @@ enum State {
 }
 
 #[allow(missing_debug_implementations)]
+#[repr(C)]
 pub struct Storage<T> {
-    state: Cell<State>,
+    // This field must be first, for correctness of `#[rustc_align_static]`
     val: UnsafeCell<T>,
+    state: Cell<State>,
 }
 
 impl<T> Storage<T> {
diff --git a/library/std/src/sys/thread_local/native/lazy.rs b/library/std/src/sys/thread_local/native/lazy.rs
index b556dd9aa25..02939a74fc0 100644
--- a/library/std/src/sys/thread_local/native/lazy.rs
+++ b/library/std/src/sys/thread_local/native/lazy.rs
@@ -27,9 +27,11 @@ enum State<D> {
 }
 
 #[allow(missing_debug_implementations)]
+#[repr(C)]
 pub struct Storage<T, D> {
-    state: Cell<State<D>>,
+    // This field must be first, for correctness of `#[rustc_align_static]`
     value: UnsafeCell<MaybeUninit<T>>,
+    state: Cell<State<D>>,
 }
 
 impl<T, D> Storage<T, D>
diff --git a/library/std/src/sys/thread_local/native/mod.rs b/library/std/src/sys/thread_local/native/mod.rs
index a5dffe3c458..5dc14240804 100644
--- a/library/std/src/sys/thread_local/native/mod.rs
+++ b/library/std/src/sys/thread_local/native/mod.rs
@@ -54,7 +54,7 @@ pub macro thread_local_inner {
     // test in `tests/thread.rs` if these types are renamed.
 
     // Used to generate the `LocalKey` value for const-initialized thread locals.
-    (@key $t:ty, const $init:expr) => {{
+    (@key $t:ty, $(#[$align_attr:meta])*, const $init:expr) => {{
         const __INIT: $t = $init;
 
         unsafe {
@@ -62,6 +62,7 @@ pub macro thread_local_inner {
                 if $crate::mem::needs_drop::<$t>() {
                     |_| {
                         #[thread_local]
+                        $(#[$align_attr])*
                         static VAL: $crate::thread::local_impl::EagerStorage<$t>
                             = $crate::thread::local_impl::EagerStorage::new(__INIT);
                         VAL.get()
@@ -69,6 +70,7 @@ pub macro thread_local_inner {
                 } else {
                     |_| {
                         #[thread_local]
+                        $(#[$align_attr])*
                         static VAL: $t = __INIT;
                         &VAL
                     }
@@ -78,7 +80,7 @@ pub macro thread_local_inner {
     }},
 
     // used to generate the `LocalKey` value for `thread_local!`
-    (@key $t:ty, $init:expr) => {{
+    (@key $t:ty, $(#[$align_attr:meta])*, $init:expr) => {{
         #[inline]
         fn __init() -> $t {
             $init
@@ -89,6 +91,7 @@ pub macro thread_local_inner {
                 if $crate::mem::needs_drop::<$t>() {
                     |init| {
                         #[thread_local]
+                        $(#[$align_attr])*
                         static VAL: $crate::thread::local_impl::LazyStorage<$t, ()>
                             = $crate::thread::local_impl::LazyStorage::new();
                         VAL.get_or_init(init, __init)
@@ -96,6 +99,7 @@ pub macro thread_local_inner {
                 } else {
                     |init| {
                         #[thread_local]
+                        $(#[$align_attr])*
                         static VAL: $crate::thread::local_impl::LazyStorage<$t, !>
                             = $crate::thread::local_impl::LazyStorage::new();
                         VAL.get_or_init(init, __init)
@@ -104,10 +108,6 @@ pub macro thread_local_inner {
             })
         }
     }},
-    ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
-        $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
-            $crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
-    },
 }
 
 #[rustc_macro_transparency = "semitransparent"]
diff --git a/library/std/src/sys/thread_local/no_threads.rs b/library/std/src/sys/thread_local/no_threads.rs
index 4da01a84acf..409dfb19518 100644
--- a/library/std/src/sys/thread_local/no_threads.rs
+++ b/library/std/src/sys/thread_local/no_threads.rs
@@ -2,6 +2,7 @@
 //! thread locals and we can instead just use plain statics!
 
 use crate::cell::{Cell, UnsafeCell};
+use crate::mem::MaybeUninit;
 use crate::ptr;
 
 #[doc(hidden)]
@@ -11,12 +12,13 @@ use crate::ptr;
 #[rustc_macro_transparency = "semitransparent"]
 pub macro thread_local_inner {
     // used to generate the `LocalKey` value for const-initialized thread locals
-    (@key $t:ty, const $init:expr) => {{
+    (@key $t:ty, $(#[$align_attr:meta])*, const $init:expr) => {{
         const __INIT: $t = $init;
 
         // NOTE: Please update the shadowing test in `tests/thread.rs` if these types are renamed.
         unsafe {
             $crate::thread::LocalKey::new(|_| {
+                $(#[$align_attr])*
                 static VAL: $crate::thread::local_impl::EagerStorage<$t> =
                     $crate::thread::local_impl::EagerStorage { value: __INIT };
                 &VAL.value
@@ -25,27 +27,22 @@ pub macro thread_local_inner {
     }},
 
     // used to generate the `LocalKey` value for `thread_local!`
-    (@key $t:ty, $init:expr) => {{
+    (@key $t:ty, $(#[$align_attr:meta])*, $init:expr) => {{
         #[inline]
         fn __init() -> $t { $init }
 
         unsafe {
-            use $crate::thread::LocalKey;
-            use $crate::thread::local_impl::LazyStorage;
-
-            LocalKey::new(|init| {
-                static VAL: LazyStorage<$t> = LazyStorage::new();
+            $crate::thread::LocalKey::new(|init| {
+                $(#[$align_attr])*
+                static VAL: $crate::thread::local_impl::LazyStorage<$t> = $crate::thread::local_impl::LazyStorage::new();
                 VAL.get(init, __init)
             })
         }
     }},
-    ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
-        $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
-            $crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
-    },
 }
 
 #[allow(missing_debug_implementations)]
+#[repr(transparent)] // Required for correctness of `#[rustc_align_static]`
 pub struct EagerStorage<T> {
     pub value: T,
 }
@@ -53,14 +50,27 @@ pub struct EagerStorage<T> {
 // SAFETY: the target doesn't have threads.
 unsafe impl<T> Sync for EagerStorage<T> {}
 
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum State {
+    Initial,
+    Alive,
+    Destroying,
+}
+
 #[allow(missing_debug_implementations)]
+#[repr(C)]
 pub struct LazyStorage<T> {
-    value: UnsafeCell<Option<T>>,
+    // This field must be first, for correctness of `#[rustc_align_static]`
+    value: UnsafeCell<MaybeUninit<T>>,
+    state: Cell<State>,
 }
 
 impl<T> LazyStorage<T> {
     pub const fn new() -> LazyStorage<T> {
-        LazyStorage { value: UnsafeCell::new(None) }
+        LazyStorage {
+            value: UnsafeCell::new(MaybeUninit::uninit()),
+            state: Cell::new(State::Initial),
+        }
     }
 
     /// Gets a pointer to the TLS value, potentially initializing it with the
@@ -70,24 +80,39 @@ impl<T> LazyStorage<T> {
     /// has occurred.
     #[inline]
     pub fn get(&'static self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
-        let value = unsafe { &*self.value.get() };
-        match value {
-            Some(v) => v,
-            None => self.initialize(i, f),
+        if self.state.get() == State::Alive {
+            self.value.get() as *const T
+        } else {
+            self.initialize(i, f)
         }
     }
 
     #[cold]
     fn initialize(&'static self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
         let value = i.and_then(Option::take).unwrap_or_else(f);
-        // Destroy the old value, after updating the TLS variable as the
-        // destructor might reference it.
+
+        // Destroy the old value if it is initialized
         // FIXME(#110897): maybe panic on recursive initialization.
+        if self.state.get() == State::Alive {
+            self.state.set(State::Destroying);
+            // Safety: we check for no initialization during drop below
+            unsafe {
+                ptr::drop_in_place(self.value.get() as *mut T);
+            }
+            self.state.set(State::Initial);
+        }
+
+        // Guard against initialization during drop
+        if self.state.get() == State::Destroying {
+            panic!("Attempted to initialize thread-local while it is being dropped");
+        }
+
         unsafe {
-            self.value.get().replace(Some(value));
+            self.value.get().write(MaybeUninit::new(value));
         }
-        // SAFETY: we just set this to `Some`.
-        unsafe { (*self.value.get()).as_ref().unwrap_unchecked() }
+        self.state.set(State::Alive);
+
+        self.value.get() as *const T
     }
 }
 
diff --git a/library/std/src/sys/thread_local/os.rs b/library/std/src/sys/thread_local/os.rs
index fe6af27db3a..88bb5ae7c65 100644
--- a/library/std/src/sys/thread_local/os.rs
+++ b/library/std/src/sys/thread_local/os.rs
@@ -1,8 +1,12 @@
 use super::key::{Key, LazyKey, get, set};
 use super::{abort_on_dtor_unwind, guard};
+use crate::alloc::{self, Layout};
 use crate::cell::Cell;
 use crate::marker::PhantomData;
-use crate::ptr;
+use crate::mem::ManuallyDrop;
+use crate::ops::Deref;
+use crate::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
+use crate::ptr::{self, NonNull};
 
 #[doc(hidden)]
 #[allow_internal_unstable(thread_local_internals)]
@@ -10,17 +14,12 @@ use crate::ptr;
 #[unstable(feature = "thread_local_internals", issue = "none")]
 #[rustc_macro_transparency = "semitransparent"]
 pub macro thread_local_inner {
-    // used to generate the `LocalKey` value for const-initialized thread locals
-    (@key $t:ty, const $init:expr) => {
-        $crate::thread::local_impl::thread_local_inner!(@key $t, { const INIT_EXPR: $t = $init; INIT_EXPR })
-    },
-
     // NOTE: we cannot import `Storage` or `LocalKey` with a `use` because that can shadow user
     // provided type or type alias with a matching name. Please update the shadowing test in
     // `tests/thread.rs` if these types are renamed.
 
     // used to generate the `LocalKey` value for `thread_local!`.
-    (@key $t:ty, $init:expr) => {{
+    (@key $t:ty, $($(#[$($align_attr:tt)*])+)?, $init:expr) => {{
         #[inline]
         fn __init() -> $t { $init }
 
@@ -29,37 +28,148 @@ pub macro thread_local_inner {
         // in `tests/thread.rs` if these types are renamed.
         unsafe {
             $crate::thread::LocalKey::new(|init| {
-                static VAL: $crate::thread::local_impl::Storage<$t>
+                static VAL: $crate::thread::local_impl::Storage<$t, {
+                    $({
+                        // Ensure that attributes have valid syntax
+                        // and that the proper feature gate is enabled
+                        $(#[$($align_attr)*])+
+                        #[allow(unused)]
+                        static DUMMY: () = ();
+                    })?
+
+                    #[allow(unused_mut)]
+                    let mut final_align = $crate::thread::local_impl::value_align::<$t>();
+                    $($($crate::thread::local_impl::thread_local_inner!(@align final_align, $($align_attr)*);)+)?
+                    final_align
+                }>
                     = $crate::thread::local_impl::Storage::new();
                 VAL.get(init, __init)
             })
         }
     }},
-    ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
-        $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
-            $crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
+
+    // process a single `rustc_align_static` attribute
+    (@align $final_align:ident, rustc_align_static($($align:tt)*) $(, $($attr_rest:tt)+)?) => {
+        let new_align: $crate::primitive::usize = $($align)*;
+        if new_align > $final_align {
+            $final_align = new_align;
+        }
+
+        $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)?
+    },
+
+    // process a single `cfg_attr` attribute
+    // by translating it into a `cfg`ed block and recursing.
+    // https://doc.rust-lang.org/reference/conditional-compilation.html#railroad-ConfigurationPredicate
+
+    (@align $final_align:ident, cfg_attr(true, $($cfg_rhs:tt)*) $(, $($attr_rest:tt)+)?) => {
+        #[cfg(true)]
+        {
+            $crate::thread::local_impl::thread_local_inner!(@align $final_align, $($cfg_rhs)*);
+        }
+
+        $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)?
+    },
+
+    (@align $final_align:ident, cfg_attr(false, $($cfg_rhs:tt)*) $(, $($attr_rest:tt)+)?) => {
+        #[cfg(false)]
+        {
+            $crate::thread::local_impl::thread_local_inner!(@align $final_align, $($cfg_rhs)*);
+        }
+
+        $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)?
+    },
+
+    (@align $final_align:ident, cfg_attr($cfg_pred:meta, $($cfg_rhs:tt)*) $(, $($attr_rest:tt)+)?) => {
+        #[cfg($cfg_pred)]
+        {
+            $crate::thread::local_impl::thread_local_inner!(@align $final_align, $($cfg_rhs)*);
+        }
+
+        $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)?
     },
 }
 
 /// Use a regular global static to store this key; the state provided will then be
 /// thread-local.
+/// INVARIANT: ALIGN must be a valid alignment, and no less than `value_align::<T>`.
 #[allow(missing_debug_implementations)]
-pub struct Storage<T> {
+pub struct Storage<T, const ALIGN: usize> {
     key: LazyKey,
     marker: PhantomData<Cell<T>>,
 }
 
-unsafe impl<T> Sync for Storage<T> {}
+unsafe impl<T, const ALIGN: usize> Sync for Storage<T, ALIGN> {}
 
+#[repr(C)]
 struct Value<T: 'static> {
+    // This field must be first, for correctness of `#[rustc_align_static]`
     value: T,
     // INVARIANT: if this value is stored under a TLS key, `key` must be that `key`.
     key: Key,
 }
 
-impl<T: 'static> Storage<T> {
-    pub const fn new() -> Storage<T> {
-        Storage { key: LazyKey::new(Some(destroy_value::<T>)), marker: PhantomData }
+pub const fn value_align<T: 'static>() -> usize {
+    crate::mem::align_of::<Value<T>>()
+}
+
+/// Equivalent to `Box<Value<T>>`, but potentially over-aligned.
+struct AlignedBox<T: 'static, const ALIGN: usize> {
+    ptr: NonNull<Value<T>>,
+}
+
+impl<T: 'static, const ALIGN: usize> AlignedBox<T, ALIGN> {
+    #[inline]
+    fn new(v: Value<T>) -> Self {
+        let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
+
+        let ptr: *mut Value<T> = (unsafe { alloc::alloc(layout) }).cast();
+        let Some(ptr) = NonNull::new(ptr) else {
+            alloc::handle_alloc_error(layout);
+        };
+        unsafe { ptr.write(v) };
+        Self { ptr }
+    }
+
+    #[inline]
+    fn into_raw(b: Self) -> *mut Value<T> {
+        let md = ManuallyDrop::new(b);
+        md.ptr.as_ptr()
+    }
+
+    #[inline]
+    unsafe fn from_raw(ptr: *mut Value<T>) -> Self {
+        Self { ptr: unsafe { NonNull::new_unchecked(ptr) } }
+    }
+}
+
+impl<T: 'static, const ALIGN: usize> Deref for AlignedBox<T, ALIGN> {
+    type Target = Value<T>;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        unsafe { &*(self.ptr.as_ptr()) }
+    }
+}
+
+impl<T: 'static, const ALIGN: usize> Drop for AlignedBox<T, ALIGN> {
+    #[inline]
+    fn drop(&mut self) {
+        let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
+
+        unsafe {
+            let unwind_result = catch_unwind(AssertUnwindSafe(|| self.ptr.drop_in_place()));
+            alloc::dealloc(self.ptr.as_ptr().cast(), layout);
+            if let Err(payload) = unwind_result {
+                resume_unwind(payload);
+            }
+        }
+    }
+}
+
+impl<T: 'static, const ALIGN: usize> Storage<T, ALIGN> {
+    pub const fn new() -> Storage<T, ALIGN> {
+        Storage { key: LazyKey::new(Some(destroy_value::<T, ALIGN>)), marker: PhantomData }
     }
 
     /// Gets a pointer to the TLS value, potentially initializing it with the
@@ -95,8 +205,11 @@ impl<T: 'static> Storage<T> {
             return ptr::null();
         }
 
-        let value = Box::new(Value { value: i.and_then(Option::take).unwrap_or_else(f), key });
-        let ptr = Box::into_raw(value);
+        let value = AlignedBox::<T, ALIGN>::new(Value {
+            value: i.and_then(Option::take).unwrap_or_else(f),
+            key,
+        });
+        let ptr = AlignedBox::into_raw(value);
 
         // SAFETY:
         // * key came from a `LazyKey` and is thus correct.
@@ -114,7 +227,7 @@ impl<T: 'static> Storage<T> {
             // initializer has already returned and the next scope only starts
             // after we return the pointer. Therefore, there can be no references
             // to the old value.
-            drop(unsafe { Box::from_raw(old) });
+            drop(unsafe { AlignedBox::<T, ALIGN>::from_raw(old) });
         }
 
         // SAFETY: We just created this value above.
@@ -122,7 +235,7 @@ impl<T: 'static> Storage<T> {
     }
 }
 
-unsafe extern "C" fn destroy_value<T: 'static>(ptr: *mut u8) {
+unsafe extern "C" fn destroy_value<T: 'static, const ALIGN: usize>(ptr: *mut u8) {
     // SAFETY:
     //
     // The OS TLS ensures that this key contains a null value when this
@@ -133,7 +246,7 @@ unsafe extern "C" fn destroy_value<T: 'static>(ptr: *mut u8) {
     // Note that to prevent an infinite loop we reset it back to null right
     // before we return from the destructor ourselves.
     abort_on_dtor_unwind(|| {
-        let ptr = unsafe { Box::from_raw(ptr as *mut Value<T>) };
+        let ptr = unsafe { AlignedBox::<T, ALIGN>::from_raw(ptr as *mut Value<T>) };
         let key = ptr.key;
         // SAFETY: `key` is the TLS key `ptr` was stored under.
         unsafe { set(key, ptr::without_provenance_mut(1)) };
diff --git a/library/std/src/thread/current.rs b/library/std/src/thread/current.rs
index 7da1621da45..f00212bfcb6 100644
--- a/library/std/src/thread/current.rs
+++ b/library/std/src/thread/current.rs
@@ -133,12 +133,32 @@ pub(super) fn set_current(thread: Thread) -> Result<(), Thread> {
     Ok(())
 }
 
-/// Gets the id of the thread that invokes it.
+/// Gets the unique identifier of the thread which invokes it.
+///
+/// Calling this function may be more efficient than accessing the current
+/// thread id through the current thread handle. i.e. `thread::current().id()`.
 ///
 /// This function will always succeed, will always return the same value for
 /// one thread and is guaranteed not to call the global allocator.
+///
+/// # Examples
+///
+/// ```
+/// #![feature(current_thread_id)]
+///
+/// use std::thread;
+///
+/// let other_thread = thread::spawn(|| {
+///     thread::current_id()
+/// });
+///
+/// let other_thread_id = other_thread.join().unwrap();
+/// assert_ne!(thread::current_id(), other_thread_id);
+/// ```
 #[inline]
-pub(crate) fn current_id() -> ThreadId {
+#[must_use]
+#[unstable(feature = "current_thread_id", issue = "147194")]
+pub fn current_id() -> ThreadId {
     // If accessing the persistent thread ID takes multiple TLS accesses, try
     // to retrieve it from the current thread handle, which will only take one
     // TLS access.
diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs
index 0a6f2e5d508..4259a4d1f3b 100644
--- a/library/std/src/thread/local.rs
+++ b/library/std/src/thread/local.rs
@@ -132,6 +132,216 @@ impl<T: 'static> fmt::Debug for LocalKey<T> {
     }
 }
 
+#[doc(hidden)]
+#[allow_internal_unstable(thread_local_internals)]
+#[unstable(feature = "thread_local_internals", issue = "none")]
+#[rustc_macro_transparency = "semitransparent"]
+pub macro thread_local_process_attrs {
+
+    // Parse `cfg_attr` to figure out whether it's a `rustc_align_static`.
+    // Each `cfg_attr` can have zero or more attributes on the RHS, and can be nested.
+
+    // finished parsing the `cfg_attr`, it had no `rustc_align_static`
+    (
+        [] [$(#[$($prev_other_attrs:tt)*])*];
+        @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [] };
+        [$($prev_align_attrs_ret:tt)*] [$($prev_other_attrs_ret:tt)*];
+        $($rest:tt)*
+    ) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs_ret)*] [$($prev_other_attrs_ret)* #[cfg_attr($($predicate)*, $($($prev_other_attrs)*),*)]];
+            $($rest)*
+        );
+    ),
+
+    // finished parsing the `cfg_attr`, it had nothing but `rustc_align_static`
+    (
+        [$(#[$($prev_align_attrs:tt)*])+] [];
+        @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [] };
+        [$($prev_align_attrs_ret:tt)*] [$($prev_other_attrs_ret:tt)*];
+        $($rest:tt)*
+    ) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs_ret)*  #[cfg_attr($($predicate)*, $($($prev_align_attrs)*),+)]] [$($prev_other_attrs_ret)*];
+            $($rest)*
+        );
+    ),
+
+    // finished parsing the `cfg_attr`, it had a mix of `rustc_align_static` and other attrs
+    (
+        [$(#[$($prev_align_attrs:tt)*])+] [$(#[$($prev_other_attrs:tt)*])+];
+        @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [] };
+        [$($prev_align_attrs_ret:tt)*] [$($prev_other_attrs_ret:tt)*];
+        $($rest:tt)*
+    ) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs_ret)*  #[cfg_attr($($predicate)*, $($($prev_align_attrs)*),+)]] [$($prev_other_attrs_ret)* #[cfg_attr($($predicate)*, $($($prev_other_attrs)*),+)]];
+            $($rest)*
+        );
+    ),
+
+    // it's a `rustc_align_static`
+    (
+        [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*];
+        @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [rustc_align_static($($align_static_args:tt)*) $(, $($attr_rhs:tt)*)?] };
+        $($rest:tt)*
+    ) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs)* #[rustc_align_static($($align_static_args)*)]] [$($prev_other_attrs)*];
+            @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] };
+            $($rest)*
+        );
+    ),
+
+    // it's a nested `cfg_attr(true, ...)`; recurse into RHS
+    (
+        [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*];
+        @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [cfg_attr(true, $($cfg_rhs:tt)*) $(, $($attr_rhs:tt)*)?] };
+        $($rest:tt)*
+    ) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [] [];
+            @processing_cfg_attr { pred: (true), rhs: [$($cfg_rhs)*] };
+            [$($prev_align_attrs)*] [$($prev_other_attrs)*];
+            @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] };
+            $($rest)*
+        );
+    ),
+
+    // it's a nested `cfg_attr(false, ...)`; recurse into RHS
+    (
+        [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*];
+        @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [cfg_attr(false, $($cfg_rhs:tt)*) $(, $($attr_rhs:tt)*)?] };
+        $($rest:tt)*
+    ) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [] [];
+            @processing_cfg_attr { pred: (false), rhs: [$($cfg_rhs)*] };
+            [$($prev_align_attrs)*] [$($prev_other_attrs)*];
+            @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] };
+            $($rest)*
+        );
+    ),
+
+
+    // it's a nested `cfg_attr(..., ...)`; recurse into RHS
+    (
+        [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*];
+        @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [cfg_attr($cfg_lhs:meta, $($cfg_rhs:tt)*) $(, $($attr_rhs:tt)*)?] };
+        $($rest:tt)*
+    ) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [] [];
+            @processing_cfg_attr { pred: ($cfg_lhs), rhs: [$($cfg_rhs)*] };
+            [$($prev_align_attrs)*] [$($prev_other_attrs)*];
+            @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] };
+            $($rest)*
+        );
+    ),
+
+    // it's some other attribute
+    (
+        [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*];
+        @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [$meta:meta $(, $($attr_rhs:tt)*)?] };
+        $($rest:tt)*
+    ) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs)*] [$($prev_other_attrs)* #[$meta]];
+            @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] };
+            $($rest)*
+        );
+    ),
+
+
+    // Separate attributes into `rustc_align_static` and everything else:
+
+    // `rustc_align_static` attribute
+    ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[rustc_align_static $($attr_rest:tt)*] $($rest:tt)*) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs)* #[rustc_align_static $($attr_rest)*]] [$($prev_other_attrs)*];
+            $($rest)*
+        );
+    ),
+
+    // `cfg_attr(true, ...)` attribute; parse it
+    ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[cfg_attr(true, $($cfg_rhs:tt)*)] $($rest:tt)*) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [] [];
+            @processing_cfg_attr { pred: (true), rhs: [$($cfg_rhs)*] };
+            [$($prev_align_attrs)*] [$($prev_other_attrs)*];
+            $($rest)*
+        );
+    ),
+
+    // `cfg_attr(false, ...)` attribute; parse it
+    ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[cfg_attr(false, $($cfg_rhs:tt)*)] $($rest:tt)*) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [] [];
+            @processing_cfg_attr { pred: (false), rhs: [$($cfg_rhs)*] };
+            [$($prev_align_attrs)*] [$($prev_other_attrs)*];
+            $($rest)*
+        );
+    ),
+
+    // `cfg_attr(..., ...)` attribute; parse it
+    ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[cfg_attr($cfg_pred:meta, $($cfg_rhs:tt)*)] $($rest:tt)*) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [] [];
+            @processing_cfg_attr { pred: ($cfg_pred), rhs: [$($cfg_rhs)*] };
+            [$($prev_align_attrs)*] [$($prev_other_attrs)*];
+            $($rest)*
+        );
+    ),
+
+    // doc comment not followed by any other attributes; process it all at once to avoid blowing recursion limit
+    ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; $(#[doc $($doc_rhs:tt)*])+ $vis:vis static $($rest:tt)*) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs)*] [$($prev_other_attrs)* $(#[doc $($doc_rhs)*])+];
+            $vis static $($rest)*
+        );
+    ),
+
+    // 8 lines of doc comment; process them all at once to avoid blowing recursion limit
+    ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*];
+     #[doc $($doc_rhs_1:tt)*] #[doc $($doc_rhs_2:tt)*] #[doc $($doc_rhs_3:tt)*] #[doc $($doc_rhs_4:tt)*]
+     #[doc $($doc_rhs_5:tt)*] #[doc $($doc_rhs_6:tt)*] #[doc $($doc_rhs_7:tt)*] #[doc $($doc_rhs_8:tt)*]
+     $($rest:tt)*) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs)*] [$($prev_other_attrs)*
+            #[doc $($doc_rhs_1)*] #[doc $($doc_rhs_2)*] #[doc $($doc_rhs_3)*] #[doc $($doc_rhs_4)*]
+            #[doc $($doc_rhs_5)*] #[doc $($doc_rhs_6)*] #[doc $($doc_rhs_7)*] #[doc $($doc_rhs_8)*]];
+            $($rest)*
+        );
+    ),
+
+    // other attribute
+    ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[$($attr:tt)*] $($rest:tt)*) => (
+        $crate::thread::local_impl::thread_local_process_attrs!(
+            [$($prev_align_attrs)*] [$($prev_other_attrs)* #[$($attr)*]];
+            $($rest)*
+        );
+    ),
+
+
+    // Delegate to `thread_local_inner` once attributes are fully categorized:
+
+    // process `const` declaration and recurse
+    ([$($align_attrs:tt)*] [$($other_attrs:tt)*]; $vis:vis static $name:ident: $t:ty = const $init:block $(; $($($rest:tt)+)?)?) => (
+        $($other_attrs)* $vis const $name: $crate::thread::LocalKey<$t> =
+            $crate::thread::local_impl::thread_local_inner!(@key $t, $($align_attrs)*, const $init);
+
+        $($($crate::thread::local_impl::thread_local_process_attrs!([] []; $($rest)+);)?)?
+    ),
+
+    // process non-`const` declaration and recurse
+    ([$($align_attrs:tt)*] [$($other_attrs:tt)*]; $vis:vis static $name:ident: $t:ty = $init:expr $(; $($($rest:tt)+)?)?) => (
+        $($other_attrs)* $vis const $name: $crate::thread::LocalKey<$t> =
+            $crate::thread::local_impl::thread_local_inner!(@key $t, $($align_attrs)*, $init);
+
+        $($($crate::thread::local_impl::thread_local_process_attrs!([] []; $($rest)+);)?)?
+    ),
+}
+
 /// Declare a new thread local storage key of type [`std::thread::LocalKey`].
 ///
 /// # Syntax
@@ -182,28 +392,11 @@ impl<T: 'static> fmt::Debug for LocalKey<T> {
 #[cfg_attr(not(test), rustc_diagnostic_item = "thread_local_macro")]
 #[allow_internal_unstable(thread_local_internals)]
 macro_rules! thread_local {
-    // empty (base case for the recursion)
     () => {};
 
-    ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const $init:block; $($rest:tt)*) => (
-        $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, const $init);
-        $crate::thread_local!($($rest)*);
-    );
-
-    ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const $init:block) => (
-        $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, const $init);
-    );
-
-    // process multiple declarations
-    ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => (
-        $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, $init);
-        $crate::thread_local!($($rest)*);
-    );
-
-    // handle a single declaration
-    ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => (
-        $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, $init);
-    );
+    ($($tt:tt)+) => {
+        $crate::thread::local_impl::thread_local_process_attrs!([] []; $($tt)+);
+    };
 }
 
 /// An error returned by [`LocalKey::try_with`](struct.LocalKey.html#method.try_with).
diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs
index 4d09b2b4e9d..fd7cce3f97d 100644
--- a/library/std/src/thread/mod.rs
+++ b/library/std/src/thread/mod.rs
@@ -183,7 +183,9 @@ mod current;
 
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use current::current;
-pub(crate) use current::{current_id, current_or_unnamed, current_os_id, drop_current};
+#[unstable(feature = "current_thread_id", issue = "147194")]
+pub use current::current_id;
+pub(crate) use current::{current_or_unnamed, current_os_id, drop_current};
 use current::{set_current, try_with_current};
 
 mod spawnhook;
@@ -205,6 +207,7 @@ pub use self::local::{AccessError, LocalKey};
 #[doc(hidden)]
 #[unstable(feature = "thread_local_internals", issue = "none")]
 pub mod local_impl {
+    pub use super::local::thread_local_process_attrs;
     pub use crate::sys::thread_local::*;
 }