diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index ec76a1a42ef09..8612a40923a8d 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -27,3 +27,5 @@ ec2cc761bc7067712ecc7734502f703fe3b024c8
 84ac80f1921afc243d71fd0caaa4f2838c294102
 # bless mir-opt tests to add `copy`
 99cb0c6bc399fb94a0ddde7e9b38e9c00d523bad
+# reformat with rustfmt edition 2024
+c682aa162b0d41e21cc6748f4fecfe01efb69d1f
diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs
index de58df3814161..1d6abbef06c95 100644
--- a/compiler/rustc_ast/src/token.rs
+++ b/compiler/rustc_ast/src/token.rs
@@ -385,35 +385,41 @@ impl TokenKind {
         Literal(Lit::new(kind, symbol, suffix))
     }
 
-    /// An approximation to proc-macro-style single-character operators used by rustc parser.
-    /// If the operator token can be broken into two tokens, the first of which is single-character,
-    /// then this function performs that operation, otherwise it returns `None`.
-    pub fn break_two_token_op(&self) -> Option<(TokenKind, TokenKind)> {
-        Some(match *self {
-            Le => (Lt, Eq),
-            EqEq => (Eq, Eq),
-            Ne => (Not, Eq),
-            Ge => (Gt, Eq),
-            AndAnd => (BinOp(And), BinOp(And)),
-            OrOr => (BinOp(Or), BinOp(Or)),
-            BinOp(Shl) => (Lt, Lt),
-            BinOp(Shr) => (Gt, Gt),
-            BinOpEq(Plus) => (BinOp(Plus), Eq),
-            BinOpEq(Minus) => (BinOp(Minus), Eq),
-            BinOpEq(Star) => (BinOp(Star), Eq),
-            BinOpEq(Slash) => (BinOp(Slash), Eq),
-            BinOpEq(Percent) => (BinOp(Percent), Eq),
-            BinOpEq(Caret) => (BinOp(Caret), Eq),
-            BinOpEq(And) => (BinOp(And), Eq),
-            BinOpEq(Or) => (BinOp(Or), Eq),
-            BinOpEq(Shl) => (Lt, Le),
-            BinOpEq(Shr) => (Gt, Ge),
-            DotDot => (Dot, Dot),
-            DotDotDot => (Dot, DotDot),
-            PathSep => (Colon, Colon),
-            RArrow => (BinOp(Minus), Gt),
-            LArrow => (Lt, BinOp(Minus)),
-            FatArrow => (Eq, Gt),
+    /// An approximation to proc-macro-style single-character operators used by
+    /// rustc parser. If the operator token can be broken into two tokens, the
+    /// first of which has `n` (1 or 2) chars, then this function performs that
+    /// operation, otherwise it returns `None`.
+    pub fn break_two_token_op(&self, n: u32) -> Option<(TokenKind, TokenKind)> {
+        assert!(n == 1 || n == 2);
+        Some(match (self, n) {
+            (Le, 1) => (Lt, Eq),
+            (EqEq, 1) => (Eq, Eq),
+            (Ne, 1) => (Not, Eq),
+            (Ge, 1) => (Gt, Eq),
+            (AndAnd, 1) => (BinOp(And), BinOp(And)),
+            (OrOr, 1) => (BinOp(Or), BinOp(Or)),
+            (BinOp(Shl), 1) => (Lt, Lt),
+            (BinOp(Shr), 1) => (Gt, Gt),
+            (BinOpEq(Plus), 1) => (BinOp(Plus), Eq),
+            (BinOpEq(Minus), 1) => (BinOp(Minus), Eq),
+            (BinOpEq(Star), 1) => (BinOp(Star), Eq),
+            (BinOpEq(Slash), 1) => (BinOp(Slash), Eq),
+            (BinOpEq(Percent), 1) => (BinOp(Percent), Eq),
+            (BinOpEq(Caret), 1) => (BinOp(Caret), Eq),
+            (BinOpEq(And), 1) => (BinOp(And), Eq),
+            (BinOpEq(Or), 1) => (BinOp(Or), Eq),
+            (BinOpEq(Shl), 1) => (Lt, Le),         // `<` + `<=`
+            (BinOpEq(Shl), 2) => (BinOp(Shl), Eq), // `<<` + `=`
+            (BinOpEq(Shr), 1) => (Gt, Ge),         // `>` + `>=`
+            (BinOpEq(Shr), 2) => (BinOp(Shr), Eq), // `>>` + `=`
+            (DotDot, 1) => (Dot, Dot),
+            (DotDotDot, 1) => (Dot, DotDot), // `.` + `..`
+            (DotDotDot, 2) => (DotDot, Dot), // `..` + `.`
+            (DotDotEq, 2) => (DotDot, Eq),
+            (PathSep, 1) => (Colon, Colon),
+            (RArrow, 1) => (BinOp(Minus), Gt),
+            (LArrow, 1) => (Lt, BinOp(Minus)),
+            (FatArrow, 1) => (Eq, Gt),
             _ => return None,
         })
     }
diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs
index eb1c1a6457856..8bd615e6d7913 100644
--- a/compiler/rustc_parse/src/parser/attr_wrapper.rs
+++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs
@@ -108,7 +108,7 @@ struct LazyAttrTokenStreamImpl {
     start_token: (Token, Spacing),
     cursor_snapshot: TokenCursor,
     num_calls: u32,
-    break_last_token: bool,
+    break_last_token: u32,
     node_replacements: Box<[NodeReplacement]>,
 }
 
@@ -339,17 +339,20 @@ impl<'a> Parser<'a> {
         let parser_replacements_end = self.capture_state.parser_replacements.len();
 
         assert!(
-            !(self.break_last_token && matches!(capture_trailing, Trailing::Yes)),
-            "Cannot set break_last_token and have trailing token"
+            !(self.break_last_token > 0 && matches!(capture_trailing, Trailing::Yes)),
+            "Cannot have break_last_token > 0 and have trailing token"
         );
+        assert!(self.break_last_token <= 2, "cannot break token more than twice");
 
         let end_pos = self.num_bump_calls
             + capture_trailing as u32
-            // If we 'broke' the last token (e.g. breaking a '>>' token to two '>' tokens), then
-            // extend the range of captured tokens to include it, since the parser was not actually
-            // bumped past it. When the `LazyAttrTokenStream` gets converted into an
-            // `AttrTokenStream`, we will create the proper token.
-            + self.break_last_token as u32;
+            // If we "broke" the last token (e.g. breaking a `>>` token once into `>` + `>`, or
+            // breaking a `>>=` token twice into `>` + `>` + `=`), then extend the range of
+            // captured tokens to include it, because the parser was not actually bumped past it.
+            // (Even if we broke twice, it was still just one token originally, hence the `1`.)
+            // When the `LazyAttrTokenStream` gets converted into an `AttrTokenStream`, we will
+            // rebreak that final token once or twice.
+            + if self.break_last_token == 0 { 0 } else { 1 };
 
         let num_calls = end_pos - collect_pos.start_pos;
 
@@ -425,7 +428,7 @@ impl<'a> Parser<'a> {
         // for the `#[cfg]` and/or `#[cfg_attr]` attrs. This allows us to run
         // eager cfg-expansion on the captured token stream.
         if definite_capture_mode {
-            assert!(!self.break_last_token, "Should not have unglued last token with cfg attr");
+            assert!(self.break_last_token == 0, "Should not have unglued last token with cfg attr");
 
             // What is the status here when parsing the example code at the top of this method?
             //
@@ -471,7 +474,7 @@ impl<'a> Parser<'a> {
 /// close delims.
 fn make_attr_token_stream(
     iter: impl Iterator<Item = FlatToken>,
-    break_last_token: bool,
+    break_last_token: u32,
 ) -> AttrTokenStream {
     #[derive(Debug)]
     struct FrameData {
@@ -513,18 +516,17 @@ fn make_attr_token_stream(
         }
     }
 
-    if break_last_token {
+    if break_last_token > 0 {
         let last_token = stack_top.inner.pop().unwrap();
         if let AttrTokenTree::Token(last_token, spacing) = last_token {
-            let unglued_first = last_token.kind.break_two_token_op().unwrap().0;
+            let (unglued, _) = last_token.kind.break_two_token_op(break_last_token).unwrap();
 
-            // An 'unglued' token is always two ASCII characters
+            // Tokens are always ASCII chars, so we can use byte arithmetic here.
             let mut first_span = last_token.span.shrink_to_lo();
-            first_span = first_span.with_hi(first_span.lo() + rustc_span::BytePos(1));
+            first_span =
+                first_span.with_hi(first_span.lo() + rustc_span::BytePos(break_last_token));
 
-            stack_top
-                .inner
-                .push(AttrTokenTree::Token(Token::new(unglued_first, first_span), spacing));
+            stack_top.inner.push(AttrTokenTree::Token(Token::new(unglued, first_span), spacing));
         } else {
             panic!("Unexpected last token {last_token:?}")
         }
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 186828baf01ed..77ad4fdeeb17b 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -146,21 +146,25 @@ pub struct Parser<'a> {
     token_cursor: TokenCursor,
     // The number of calls to `bump`, i.e. the position in the token stream.
     num_bump_calls: u32,
-    // During parsing we may sometimes need to 'unglue' a glued token into two
-    // component tokens (e.g. '>>' into '>' and '>), so the parser can consume
-    // them one at a time. This process bypasses the normal capturing mechanism
-    // (e.g. `num_bump_calls` will not be incremented), since the 'unglued'
-    // tokens due not exist in the original `TokenStream`.
+    // During parsing we may sometimes need to "unglue" a glued token into two
+    // or three component tokens (e.g. `>>` into `>` and `>`, or `>>=` into `>`
+    // and `>` and `=`), so the parser can consume them one at a time. This
+    // process bypasses the normal capturing mechanism (e.g. `num_bump_calls`
+    // will not be incremented), since the "unglued" tokens due not exist in
+    // the original `TokenStream`.
     //
-    // If we end up consuming both unglued tokens, this is not an issue. We'll
-    // end up capturing the single 'glued' token.
+    // If we end up consuming all the component tokens, this is not an issue,
+    // because we'll end up capturing the single "glued" token.
     //
-    // However, sometimes we may want to capture just the first 'unglued'
+    // However, sometimes we may want to capture not all of the original
     // token. For example, capturing the `Vec<u8>` in `Option<Vec<u8>>`
     // requires us to unglue the trailing `>>` token. The `break_last_token`
-    // field is used to track this token. It gets appended to the captured
+    // field is used to track these tokens. They get appended to the captured
     // stream when we evaluate a `LazyAttrTokenStream`.
-    break_last_token: bool,
+    //
+    // This value is always 0, 1, or 2. It can only reach 2 when splitting
+    // `>>=` or `<<=`.
+    break_last_token: u32,
     /// This field is used to keep track of how many left angle brackets we have seen. This is
     /// required in order to detect extra leading left angle brackets (`<` characters) and error
     /// appropriately.
@@ -453,7 +457,7 @@ impl<'a> Parser<'a> {
             expected_tokens: Vec::new(),
             token_cursor: TokenCursor { tree_cursor: stream.into_trees(), stack: Vec::new() },
             num_bump_calls: 0,
-            break_last_token: false,
+            break_last_token: 0,
             unmatched_angle_bracket_count: 0,
             angle_bracket_nesting: 0,
             last_unexpected_token_span: None,
@@ -773,7 +777,7 @@ impl<'a> Parser<'a> {
             self.bump();
             return true;
         }
-        match self.token.kind.break_two_token_op() {
+        match self.token.kind.break_two_token_op(1) {
             Some((first, second)) if first == expected => {
                 let first_span = self.psess.source_map().start_point(self.token.span);
                 let second_span = self.token.span.with_lo(first_span.hi());
@@ -783,8 +787,8 @@ impl<'a> Parser<'a> {
                 //
                 // If we consume any additional tokens, then this token
                 // is not needed (we'll capture the entire 'glued' token),
-                // and `bump` will set this field to `None`
-                self.break_last_token = true;
+                // and `bump` will set this field to 0.
+                self.break_last_token += 1;
                 // Use the spacing of the glued token as the spacing of the
                 // unglued second token.
                 self.bump_with((Token::new(second, second_span), self.token_spacing));
@@ -1148,10 +1152,9 @@ impl<'a> Parser<'a> {
         // than `.0`/`.1` access.
         let mut next = self.token_cursor.inlined_next();
         self.num_bump_calls += 1;
-        // We've retrieved an token from the underlying
-        // cursor, so we no longer need to worry about
-        // an unglued token. See `break_and_eat` for more details
-        self.break_last_token = false;
+        // We got a token from the underlying cursor and no longer need to
+        // worry about an unglued token. See `break_and_eat` for more details.
+        self.break_last_token = 0;
         if next.0.span.is_dummy() {
             // Tweak the location for better diagnostics, but keep syntactic context intact.
             let fallback_span = self.token.span;
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 012962dab4257..fbf1c8d60108c 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -1690,12 +1690,8 @@ supported_targets! {
     ("x86_64h-apple-darwin", x86_64h_apple_darwin),
     ("i686-apple-darwin", i686_apple_darwin),
 
-    // FIXME(#106649): Remove aarch64-fuchsia in favor of aarch64-unknown-fuchsia
-    ("aarch64-fuchsia", aarch64_fuchsia),
     ("aarch64-unknown-fuchsia", aarch64_unknown_fuchsia),
     ("riscv64gc-unknown-fuchsia", riscv64gc_unknown_fuchsia),
-    // FIXME(#106649): Remove x86_64-fuchsia in favor of x86_64-unknown-fuchsia
-    ("x86_64-fuchsia", x86_64_fuchsia),
     ("x86_64-unknown-fuchsia", x86_64_unknown_fuchsia),
 
     ("avr-unknown-gnu-atmega328", avr_unknown_gnu_atmega328),
diff --git a/compiler/rustc_target/src/spec/targets/aarch64_fuchsia.rs b/compiler/rustc_target/src/spec/targets/aarch64_fuchsia.rs
deleted file mode 100644
index 144ac85622eef..0000000000000
--- a/compiler/rustc_target/src/spec/targets/aarch64_fuchsia.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub(crate) use crate::spec::targets::aarch64_unknown_fuchsia::target;
diff --git a/compiler/rustc_target/src/spec/targets/x86_64_fuchsia.rs b/compiler/rustc_target/src/spec/targets/x86_64_fuchsia.rs
deleted file mode 100644
index ce3e1e159b7d7..0000000000000
--- a/compiler/rustc_target/src/spec/targets/x86_64_fuchsia.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub(crate) use crate::spec::targets::x86_64_unknown_fuchsia::target;
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index a30b57c19d402..d9b03c9707285 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -394,6 +394,8 @@ pub mod panicking;
 #[unstable(feature = "core_pattern_types", issue = "123646")]
 pub mod pat;
 pub mod pin;
+#[unstable(feature = "random", issue = "130703")]
+pub mod random;
 #[unstable(feature = "new_range_api", issue = "125687")]
 pub mod range;
 pub mod result;
diff --git a/library/core/src/random.rs b/library/core/src/random.rs
new file mode 100644
index 0000000000000..051fe26086389
--- /dev/null
+++ b/library/core/src/random.rs
@@ -0,0 +1,62 @@
+//! Random value generation.
+//!
+//! The [`Random`] trait allows generating a random value for a type using a
+//! given [`RandomSource`].
+
+/// A source of randomness.
+#[unstable(feature = "random", issue = "130703")]
+pub trait RandomSource {
+    /// Fills `bytes` with random bytes.
+    fn fill_bytes(&mut self, bytes: &mut [u8]);
+}
+
+/// A trait for getting a random value for a type.
+///
+/// **Warning:** Be careful when manipulating random values! The
+/// [`random`](Random::random) method on integers samples them with a uniform
+/// distribution, so a value of 1 is just as likely as [`i32::MAX`]. By using
+/// modulo operations, some of the resulting values can become more likely than
+/// others. Use audited crates when in doubt.
+#[unstable(feature = "random", issue = "130703")]
+pub trait Random: Sized {
+    /// Generates a random value.
+    fn random(source: &mut (impl RandomSource + ?Sized)) -> Self;
+}
+
+impl Random for bool {
+    fn random(source: &mut (impl RandomSource + ?Sized)) -> Self {
+        u8::random(source) & 1 == 1
+    }
+}
+
+macro_rules! impl_primitive {
+    ($t:ty) => {
+        impl Random for $t {
+            /// Generates a random value.
+            ///
+            /// **Warning:** Be careful when manipulating the resulting value! This
+            /// method samples according to a uniform distribution, so a value of 1 is
+            /// just as likely as [`MAX`](Self::MAX). By using modulo operations, some
+            /// values can become more likely than others. Use audited crates when in
+            /// doubt.
+            fn random(source: &mut (impl RandomSource + ?Sized)) -> Self {
+                let mut bytes = (0 as Self).to_ne_bytes();
+                source.fill_bytes(&mut bytes);
+                Self::from_ne_bytes(bytes)
+            }
+        }
+    };
+}
+
+impl_primitive!(u8);
+impl_primitive!(i8);
+impl_primitive!(u16);
+impl_primitive!(i16);
+impl_primitive!(u32);
+impl_primitive!(i32);
+impl_primitive!(u64);
+impl_primitive!(i64);
+impl_primitive!(u128);
+impl_primitive!(i128);
+impl_primitive!(usize);
+impl_primitive!(isize);
diff --git a/library/std/src/hash/random.rs b/library/std/src/hash/random.rs
index 8ef45172eac40..40f3a90f60c8a 100644
--- a/library/std/src/hash/random.rs
+++ b/library/std/src/hash/random.rs
@@ -10,7 +10,8 @@
 #[allow(deprecated)]
 use super::{BuildHasher, Hasher, SipHasher13};
 use crate::cell::Cell;
-use crate::{fmt, sys};
+use crate::fmt;
+use crate::sys::random::hashmap_random_keys;
 
 /// `RandomState` is the default state for [`HashMap`] types.
 ///
@@ -65,7 +66,7 @@ impl RandomState {
         // increment one of the seeds on every RandomState creation, giving
         // every corresponding HashMap a different iteration order.
         thread_local!(static KEYS: Cell<(u64, u64)> = {
-            Cell::new(sys::hashmap_random_keys())
+            Cell::new(hashmap_random_keys())
         });
 
         KEYS.with(|keys| {
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 2add88da9a7a3..e6b2f15a7a681 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -318,6 +318,7 @@
 //
 // Library features (core):
 // tidy-alphabetical-start
+#![feature(array_chunks)]
 #![feature(c_str_module)]
 #![feature(char_internals)]
 #![feature(clone_to_uninit)]
@@ -348,6 +349,7 @@
 #![feature(prelude_2024)]
 #![feature(ptr_as_uninit)]
 #![feature(ptr_mask)]
+#![feature(random)]
 #![feature(slice_internals)]
 #![feature(slice_ptr_get)]
 #![feature(slice_range)]
@@ -595,6 +597,8 @@ pub mod path;
 #[unstable(feature = "anonymous_pipe", issue = "127154")]
 pub mod pipe;
 pub mod process;
+#[unstable(feature = "random", issue = "130703")]
+pub mod random;
 pub mod sync;
 pub mod time;
 
diff --git a/library/std/src/random.rs b/library/std/src/random.rs
new file mode 100644
index 0000000000000..ecbf02eee8430
--- /dev/null
+++ b/library/std/src/random.rs
@@ -0,0 +1,104 @@
+//! Random value generation.
+//!
+//! The [`Random`] trait allows generating a random value for a type using a
+//! given [`RandomSource`].
+
+#[unstable(feature = "random", issue = "130703")]
+pub use core::random::*;
+
+use crate::sys::random as sys;
+
+/// The default random source.
+///
+/// This asks the system for random data suitable for cryptographic purposes
+/// such as key generation. If security is a concern, consult the platform
+/// documentation below for the specific guarantees your target provides.
+///
+/// The high quality of randomness provided by this source means it can be quite
+/// slow on some targets. If you need a large quantity of random numbers and
+/// security is not a concern,  consider using an alternative random number
+/// generator (potentially seeded from this one).
+///
+/// # Underlying sources
+///
+/// Platform               | Source
+/// -----------------------|---------------------------------------------------------------
+/// Linux                  | [`getrandom`] or [`/dev/urandom`] after polling `/dev/random`
+/// Windows                | [`ProcessPrng`](https://learn.microsoft.com/en-us/windows/win32/seccng/processprng)
+/// Apple                  | `CCRandomGenerateBytes`
+/// DragonFly              | [`arc4random_buf`](https://man.dragonflybsd.org/?command=arc4random)
+/// ESP-IDF                | [`esp_fill_random`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html#_CPPv415esp_fill_randomPv6size_t)
+/// FreeBSD                | [`arc4random_buf`](https://man.freebsd.org/cgi/man.cgi?query=arc4random)
+/// Fuchsia                | [`cprng_draw`](https://fuchsia.dev/reference/syscalls/cprng_draw)
+/// Haiku                  | `arc4random_buf`
+/// Illumos                | [`arc4random_buf`](https://www.illumos.org/man/3C/arc4random)
+/// NetBSD                 | [`arc4random_buf`](https://man.netbsd.org/arc4random.3)
+/// OpenBSD                | [`arc4random_buf`](https://man.openbsd.org/arc4random.3)
+/// Solaris                | [`arc4random_buf`](https://docs.oracle.com/cd/E88353_01/html/E37843/arc4random-3c.html)
+/// Vita                   | `arc4random_buf`
+/// Hermit                 | `read_entropy`
+/// Horizon                | `getrandom` shim
+/// Hurd, L4Re, QNX        | `/dev/urandom`
+/// Redox                  | `/scheme/rand`
+/// SGX                    | [`rdrand`](https://en.wikipedia.org/wiki/RDRAND)
+/// SOLID                  | `SOLID_RNG_SampleRandomBytes`
+/// TEEOS                  | `TEE_GenerateRandom`
+/// UEFI                   | [`EFI_RNG_PROTOCOL`](https://uefi.org/specs/UEFI/2.10/37_Secure_Technologies.html#random-number-generator-protocol)
+/// VxWorks                | `randABytes` after waiting for `randSecure` to become ready
+/// WASI                   | [`random_get`](https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-random_getbuf-pointeru8-buf_len-size---result-errno)
+/// ZKVM                   | `sys_rand`
+///
+/// Note that the sources used might change over time.
+///
+/// Consult the documentation for the underlying operations on your supported
+/// targets to determine whether they provide any particular desired properties,
+/// such as support for reseeding on VM fork operations.
+///
+/// [`getrandom`]: https://www.man7.org/linux/man-pages/man2/getrandom.2.html
+/// [`/dev/urandom`]: https://www.man7.org/linux/man-pages/man4/random.4.html
+#[derive(Default, Debug, Clone, Copy)]
+#[unstable(feature = "random", issue = "130703")]
+pub struct DefaultRandomSource;
+
+#[unstable(feature = "random", issue = "130703")]
+impl RandomSource for DefaultRandomSource {
+    fn fill_bytes(&mut self, bytes: &mut [u8]) {
+        sys::fill_bytes(bytes)
+    }
+}
+
+/// Generates a random value with the default random source.
+///
+/// This is a convenience function for `T::random(&mut DefaultRandomSource)` and
+/// will sample according to the same distribution as the underlying [`Random`]
+/// trait implementation.
+///
+/// **Warning:** Be careful when manipulating random values! The
+/// [`random`](Random::random) method on integers samples them with a uniform
+/// distribution, so a value of 1 is just as likely as [`i32::MAX`]. By using
+/// modulo operations, some of the resulting values can become more likely than
+/// others. Use audited crates when in doubt.
+///
+/// # Examples
+///
+/// Generating a [version 4/variant 1 UUID] represented as text:
+/// ```
+/// #![feature(random)]
+///
+/// use std::random::random;
+///
+/// let bits: u128 = random();
+/// let g1 = (bits >> 96) as u32;
+/// let g2 = (bits >> 80) as u16;
+/// let g3 = (0x4000 | (bits >> 64) & 0x0fff) as u16;
+/// let g4 = (0x8000 | (bits >> 48) & 0x3fff) as u16;
+/// let g5 = (bits & 0xffffffffffff) as u64;
+/// let uuid = format!("{g1:08x}-{g2:04x}-{g3:04x}-{g4:04x}-{g5:012x}");
+/// println!("{uuid}");
+/// ```
+///
+/// [version 4/variant 1 UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)
+#[unstable(feature = "random", issue = "130703")]
+pub fn random<T: Random>() -> T {
+    T::random(&mut DefaultRandomSource)
+}
diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs
index 96d6f2c87c4c1..df25b84fbbe5a 100644
--- a/library/std/src/sys/mod.rs
+++ b/library/std/src/sys/mod.rs
@@ -15,6 +15,7 @@ pub mod dbg;
 pub mod exit_guard;
 pub mod os_str;
 pub mod path;
+pub mod random;
 pub mod sync;
 pub mod thread_local;
 
diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs
index 1f2e5d9469f5c..f49ef94717499 100644
--- a/library/std/src/sys/pal/hermit/mod.rs
+++ b/library/std/src/sys/pal/hermit/mod.rs
@@ -52,20 +52,6 @@ pub fn abort_internal() -> ! {
     unsafe { hermit_abi::abort() }
 }
 
-pub fn hashmap_random_keys() -> (u64, u64) {
-    let mut buf = [0; 16];
-    let mut slice = &mut buf[..];
-    while !slice.is_empty() {
-        let res = cvt(unsafe { hermit_abi::read_entropy(slice.as_mut_ptr(), slice.len(), 0) })
-            .expect("failed to generate random hashmap keys");
-        slice = &mut slice[res as usize..];
-    }
-
-    let key1 = buf[..8].try_into().unwrap();
-    let key2 = buf[8..].try_into().unwrap();
-    (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2))
-}
-
 // This function is needed by the panic runtime. The symbol is named in
 // pre-link args for the target specification, so keep that in sync.
 #[cfg(not(test))]
diff --git a/library/std/src/sys/pal/sgx/abi/usercalls/mod.rs b/library/std/src/sys/pal/sgx/abi/usercalls/mod.rs
index def1ccdf81ac0..90b9b59471a52 100644
--- a/library/std/src/sys/pal/sgx/abi/usercalls/mod.rs
+++ b/library/std/src/sys/pal/sgx/abi/usercalls/mod.rs
@@ -1,6 +1,6 @@
 use crate::cmp;
 use crate::io::{Error as IoError, ErrorKind, IoSlice, IoSliceMut, Result as IoResult};
-use crate::sys::rand::rdrand64;
+use crate::random::{DefaultRandomSource, Random};
 use crate::time::{Duration, Instant};
 
 pub(crate) mod alloc;
@@ -164,7 +164,7 @@ pub fn wait(event_mask: u64, mut timeout: u64) -> IoResult<u64> {
         // trusted to ensure accurate timeouts.
         if let Ok(timeout_signed) = i64::try_from(timeout) {
             let tenth = timeout_signed / 10;
-            let deviation = (rdrand64() as i64).checked_rem(tenth).unwrap_or(0);
+            let deviation = i64::random(&mut DefaultRandomSource).checked_rem(tenth).unwrap_or(0);
             timeout = timeout_signed.saturating_add(deviation) as _;
         }
     }
diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs
index 8d29b2ec6193e..586ccd18c2f57 100644
--- a/library/std/src/sys/pal/sgx/mod.rs
+++ b/library/std/src/sys/pal/sgx/mod.rs
@@ -132,24 +132,6 @@ pub extern "C" fn __rust_abort() {
     abort_internal();
 }
 
-pub mod rand {
-    pub fn rdrand64() -> u64 {
-        unsafe {
-            let mut ret: u64 = 0;
-            for _ in 0..10 {
-                if crate::arch::x86_64::_rdrand64_step(&mut ret) == 1 {
-                    return ret;
-                }
-            }
-            rtabort!("Failed to obtain random data");
-        }
-    }
-}
-
-pub fn hashmap_random_keys() -> (u64, u64) {
-    (self::rand::rdrand64(), self::rand::rdrand64())
-}
-
 pub use crate::sys_common::{AsInner, FromInner, IntoInner};
 
 pub trait TryIntoInner<Inner>: Sized {
diff --git a/library/std/src/sys/pal/solid/mod.rs b/library/std/src/sys/pal/solid/mod.rs
index 6ebcf5b7c48c8..d41042be51844 100644
--- a/library/std/src/sys/pal/solid/mod.rs
+++ b/library/std/src/sys/pal/solid/mod.rs
@@ -62,13 +62,3 @@ pub fn decode_error_kind(code: i32) -> crate::io::ErrorKind {
 pub fn abort_internal() -> ! {
     unsafe { libc::abort() }
 }
-
-pub fn hashmap_random_keys() -> (u64, u64) {
-    unsafe {
-        let mut out = crate::mem::MaybeUninit::<[u64; 2]>::uninit();
-        let result = abi::SOLID_RNG_SampleRandomBytes(out.as_mut_ptr() as *mut u8, 16);
-        assert_eq!(result, 0, "SOLID_RNG_SampleRandomBytes failed: {result}");
-        let [x1, x2] = out.assume_init();
-        (x1, x2)
-    }
-}
diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs
index 00e3860424006..60a227afb84e3 100644
--- a/library/std/src/sys/pal/teeos/mod.rs
+++ b/library/std/src/sys/pal/teeos/mod.rs
@@ -6,8 +6,6 @@
 #![allow(unused_variables)]
 #![allow(dead_code)]
 
-pub use self::rand::hashmap_random_keys;
-
 #[path = "../unsupported/args.rs"]
 pub mod args;
 #[path = "../unsupported/env.rs"]
@@ -23,7 +21,6 @@ pub mod os;
 pub mod pipe;
 #[path = "../unsupported/process.rs"]
 pub mod process;
-mod rand;
 pub mod stdio;
 pub mod thread;
 #[allow(non_upper_case_globals)]
diff --git a/library/std/src/sys/pal/teeos/rand.rs b/library/std/src/sys/pal/teeos/rand.rs
deleted file mode 100644
index b45c3bb40e782..0000000000000
--- a/library/std/src/sys/pal/teeos/rand.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-pub fn hashmap_random_keys() -> (u64, u64) {
-    const KEY_LEN: usize = core::mem::size_of::<u64>();
-
-    let mut v = [0u8; KEY_LEN * 2];
-    imp::fill_bytes(&mut v);
-
-    let key1 = v[0..KEY_LEN].try_into().unwrap();
-    let key2 = v[KEY_LEN..].try_into().unwrap();
-
-    (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2))
-}
-
-mod imp {
-    extern "C" {
-        fn TEE_GenerateRandom(randomBuffer: *mut core::ffi::c_void, randomBufferLen: libc::size_t);
-    }
-
-    pub fn fill_bytes(v: &mut [u8]) {
-        unsafe { TEE_GenerateRandom(v.as_mut_ptr() as _, v.len() * crate::mem::size_of::<u8>()) }
-    }
-}
diff --git a/library/std/src/sys/pal/uefi/mod.rs b/library/std/src/sys/pal/uefi/mod.rs
index ac22f4ded8855..c0ab52f650aa5 100644
--- a/library/std/src/sys/pal/uefi/mod.rs
+++ b/library/std/src/sys/pal/uefi/mod.rs
@@ -179,39 +179,6 @@ pub extern "C" fn __rust_abort() {
     abort_internal();
 }
 
-#[inline]
-pub fn hashmap_random_keys() -> (u64, u64) {
-    get_random().unwrap()
-}
-
-fn get_random() -> Option<(u64, u64)> {
-    use r_efi::protocols::rng;
-
-    let mut buf = [0u8; 16];
-    let handles = helpers::locate_handles(rng::PROTOCOL_GUID).ok()?;
-    for handle in handles {
-        if let Ok(protocol) = helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
-            let r = unsafe {
-                ((*protocol.as_ptr()).get_rng)(
-                    protocol.as_ptr(),
-                    crate::ptr::null_mut(),
-                    buf.len(),
-                    buf.as_mut_ptr(),
-                )
-            };
-            if r.is_error() {
-                continue;
-            } else {
-                return Some((
-                    u64::from_le_bytes(buf[..8].try_into().ok()?),
-                    u64::from_le_bytes(buf[8..].try_into().ok()?),
-                ));
-            }
-        }
-    }
-    None
-}
-
 /// Disable access to BootServices if `EVT_SIGNAL_EXIT_BOOT_SERVICES` is signaled
 extern "efiapi" fn exit_boot_service_handler(_e: r_efi::efi::Event, _ctx: *mut crate::ffi::c_void) {
     uefi::env::disable_boot_services();
diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs
index e8428eccb1691..1c9159e5fba86 100644
--- a/library/std/src/sys/pal/unix/mod.rs
+++ b/library/std/src/sys/pal/unix/mod.rs
@@ -1,6 +1,5 @@
 #![allow(missing_docs, nonstandard_style)]
 
-pub use self::rand::hashmap_random_keys;
 use crate::io::ErrorKind;
 
 #[cfg(not(target_os = "espidf"))]
@@ -26,7 +25,6 @@ pub use self::l4re::net;
 pub mod os;
 pub mod pipe;
 pub mod process;
-pub mod rand;
 pub mod stack_overflow;
 pub mod stdio;
 pub mod thread;
diff --git a/library/std/src/sys/pal/unix/rand.rs b/library/std/src/sys/pal/unix/rand.rs
deleted file mode 100644
index cc0852aab4396..0000000000000
--- a/library/std/src/sys/pal/unix/rand.rs
+++ /dev/null
@@ -1,302 +0,0 @@
-pub fn hashmap_random_keys() -> (u64, u64) {
-    const KEY_LEN: usize = core::mem::size_of::<u64>();
-
-    let mut v = [0u8; KEY_LEN * 2];
-    if let Err(err) = read(&mut v) {
-        panic!("failed to retrieve random hash map seed: {err}");
-    }
-
-    let key1 = v[0..KEY_LEN].try_into().unwrap();
-    let key2 = v[KEY_LEN..].try_into().unwrap();
-
-    (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2))
-}
-
-cfg_if::cfg_if! {
-    if #[cfg(any(
-        target_vendor = "apple",
-        target_os = "openbsd",
-        target_os = "emscripten",
-        target_os = "vita",
-        all(target_os = "netbsd", not(netbsd10)),
-        target_os = "fuchsia",
-        target_os = "vxworks",
-    ))] {
-        // Some systems have a syscall that directly retrieves random data.
-        // If that is guaranteed to be available, use it.
-        use imp::syscall as read;
-    } else {
-        // Otherwise, try the syscall to see if it exists only on some systems
-        // and fall back to reading from the random device otherwise.
-        fn read(bytes: &mut [u8]) -> crate::io::Result<()> {
-            use crate::fs::File;
-            use crate::io::Read;
-            use crate::sync::OnceLock;
-
-            #[cfg(any(
-                target_os = "linux",
-                target_os = "android",
-                target_os = "espidf",
-                target_os = "horizon",
-                target_os = "freebsd",
-                target_os = "dragonfly",
-                target_os = "solaris",
-                target_os = "illumos",
-                netbsd10,
-            ))]
-            if let Some(res) = imp::syscall(bytes) {
-                return res;
-            }
-
-            const PATH: &'static str = if cfg!(target_os = "redox") {
-                "/scheme/rand"
-            } else {
-                "/dev/urandom"
-            };
-
-            static FILE: OnceLock<File> = OnceLock::new();
-
-            FILE.get_or_try_init(|| File::open(PATH))?.read_exact(bytes)
-        }
-    }
-}
-
-// All these systems a `getrandom` syscall.
-//
-// It is not guaranteed to be available, so return None to fallback to the file
-// implementation.
-#[cfg(any(
-    target_os = "linux",
-    target_os = "android",
-    target_os = "espidf",
-    target_os = "horizon",
-    target_os = "freebsd",
-    target_os = "dragonfly",
-    target_os = "solaris",
-    target_os = "illumos",
-    netbsd10,
-))]
-mod imp {
-    use crate::io::{Error, Result};
-    use crate::sync::atomic::{AtomicBool, Ordering};
-    use crate::sys::os::errno;
-
-    #[cfg(any(target_os = "linux", target_os = "android"))]
-    fn getrandom(buf: &mut [u8]) -> libc::ssize_t {
-        use crate::sys::weak::syscall;
-
-        // A weak symbol allows interposition, e.g. for perf measurements that want to
-        // disable randomness for consistency. Otherwise, we'll try a raw syscall.
-        // (`getrandom` was added in glibc 2.25, musl 1.1.20, android API level 28)
-        syscall! {
-            fn getrandom(
-                buffer: *mut libc::c_void,
-                length: libc::size_t,
-                flags: libc::c_uint
-            ) -> libc::ssize_t
-        }
-
-        // This provides the best quality random numbers available at the given moment
-        // without ever blocking, and is preferable to falling back to /dev/urandom.
-        static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true);
-        if GRND_INSECURE_AVAILABLE.load(Ordering::Relaxed) {
-            let ret = unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_INSECURE) };
-            if ret == -1 && errno() as libc::c_int == libc::EINVAL {
-                GRND_INSECURE_AVAILABLE.store(false, Ordering::Relaxed);
-            } else {
-                return ret;
-            }
-        }
-
-        unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_NONBLOCK) }
-    }
-
-    #[cfg(any(
-        target_os = "dragonfly",
-        target_os = "espidf",
-        target_os = "horizon",
-        target_os = "freebsd",
-        netbsd10,
-        target_os = "illumos",
-        target_os = "solaris"
-    ))]
-    fn getrandom(buf: &mut [u8]) -> libc::ssize_t {
-        unsafe { libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) }
-    }
-
-    pub fn syscall(v: &mut [u8]) -> Option<Result<()>> {
-        static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false);
-
-        if GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed) {
-            return None;
-        }
-
-        let mut read = 0;
-        while read < v.len() {
-            let result = getrandom(&mut v[read..]);
-            if result == -1 {
-                let err = errno() as libc::c_int;
-                if err == libc::EINTR {
-                    continue;
-                } else if err == libc::ENOSYS || err == libc::EPERM {
-                    // `getrandom` is not supported on the current system.
-                    //
-                    // Also fall back in case it is disabled by something like
-                    // seccomp or inside of docker.
-                    //
-                    // If the `getrandom` syscall is not implemented in the current kernel version it should return an
-                    // `ENOSYS` error. Docker also blocks the whole syscall inside unprivileged containers, and
-                    // returns `EPERM` (instead of `ENOSYS`) when a program tries to invoke the syscall. Because of
-                    // that we need to check for *both* `ENOSYS` and `EPERM`.
-                    //
-                    // Note that Docker's behavior is breaking other projects (notably glibc), so they're planning
-                    // to update their filtering to return `ENOSYS` in a future release:
-                    //
-                    //     https://github.com/moby/moby/issues/42680
-                    //
-                    GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed);
-                    return None;
-                } else if err == libc::EAGAIN {
-                    // getrandom has failed because it would have blocked as the
-                    // non-blocking pool (urandom) has not been initialized in
-                    // the kernel yet due to a lack of entropy. Fallback to
-                    // reading from `/dev/urandom` which will return potentially
-                    // insecure random data to avoid blocking applications which
-                    // could depend on this call without ever knowing they do and
-                    // don't have a work around.
-                    return None;
-                } else {
-                    return Some(Err(Error::from_raw_os_error(err)));
-                }
-            } else {
-                read += result as usize;
-            }
-        }
-
-        Some(Ok(()))
-    }
-}
-
-#[cfg(any(
-    target_os = "macos", // Supported since macOS 10.12+.
-    target_os = "openbsd",
-    target_os = "emscripten",
-    target_os = "vita",
-))]
-mod imp {
-    use crate::io::{Error, Result};
-
-    pub fn syscall(v: &mut [u8]) -> Result<()> {
-        // getentropy(2) permits a maximum buffer size of 256 bytes
-        for s in v.chunks_mut(256) {
-            let ret = unsafe { libc::getentropy(s.as_mut_ptr().cast(), s.len()) };
-            if ret == -1 {
-                return Err(Error::last_os_error());
-            }
-        }
-
-        Ok(())
-    }
-}
-
-// On Apple platforms, `CCRandomGenerateBytes` and `SecRandomCopyBytes` simply
-// call into `CCRandomCopyBytes` with `kCCRandomDefault`. `CCRandomCopyBytes`
-// manages a CSPRNG which is seeded from the kernel's CSPRNG and which runs on
-// its own thread accessed via GCD. This seems needlessly heavyweight for our purposes
-// so we only use it when `getentropy` is blocked, which appears to be the case
-// on all platforms except macOS (see #102643).
-//
-// `CCRandomGenerateBytes` is used instead of `SecRandomCopyBytes` because the former is accessible
-// via `libSystem` (libc) while the other needs to link to `Security.framework`.
-#[cfg(all(target_vendor = "apple", not(target_os = "macos")))]
-mod imp {
-    use libc::size_t;
-
-    use crate::ffi::{c_int, c_void};
-    use crate::io::{Error, Result};
-
-    pub fn syscall(v: &mut [u8]) -> Result<()> {
-        extern "C" {
-            fn CCRandomGenerateBytes(bytes: *mut c_void, count: size_t) -> c_int;
-        }
-
-        let ret = unsafe { CCRandomGenerateBytes(v.as_mut_ptr().cast(), v.len()) };
-        if ret != -1 { Ok(()) } else { Err(Error::last_os_error()) }
-    }
-}
-
-// FIXME: once the 10.x release becomes the minimum, this can be dropped for simplification.
-#[cfg(all(target_os = "netbsd", not(netbsd10)))]
-mod imp {
-    use crate::io::{Error, Result};
-    use crate::ptr;
-
-    pub fn syscall(v: &mut [u8]) -> Result<()> {
-        let mib = [libc::CTL_KERN, libc::KERN_ARND];
-        // kern.arandom permits a maximum buffer size of 256 bytes
-        for s in v.chunks_mut(256) {
-            let mut s_len = s.len();
-            let ret = unsafe {
-                libc::sysctl(
-                    mib.as_ptr(),
-                    mib.len() as libc::c_uint,
-                    s.as_mut_ptr() as *mut _,
-                    &mut s_len,
-                    ptr::null(),
-                    0,
-                )
-            };
-            if ret == -1 {
-                return Err(Error::last_os_error());
-            } else if s_len != s.len() {
-                // FIXME(joboet): this can't actually happen, can it?
-                panic!("read less bytes than requested from kern.arandom");
-            }
-        }
-
-        Ok(())
-    }
-}
-
-#[cfg(target_os = "fuchsia")]
-mod imp {
-    use crate::io::Result;
-
-    #[link(name = "zircon")]
-    extern "C" {
-        fn zx_cprng_draw(buffer: *mut u8, len: usize);
-    }
-
-    pub fn syscall(v: &mut [u8]) -> Result<()> {
-        unsafe { zx_cprng_draw(v.as_mut_ptr(), v.len()) };
-        Ok(())
-    }
-}
-
-#[cfg(target_os = "vxworks")]
-mod imp {
-    use core::sync::atomic::AtomicBool;
-    use core::sync::atomic::Ordering::Relaxed;
-
-    use crate::io::{Error, Result};
-
-    pub fn syscall(v: &mut [u8]) -> Result<()> {
-        static RNG_INIT: AtomicBool = AtomicBool::new(false);
-        while !RNG_INIT.load(Relaxed) {
-            let ret = unsafe { libc::randSecure() };
-            if ret < 0 {
-                return Err(Error::last_os_error());
-            } else if ret > 0 {
-                RNG_INIT.store(true, Relaxed);
-                break;
-            }
-
-            unsafe { libc::usleep(10) };
-        }
-
-        let ret = unsafe {
-            libc::randABytes(v.as_mut_ptr() as *mut libc::c_uchar, v.len() as libc::c_int)
-        };
-        if ret >= 0 { Ok(()) } else { Err(Error::last_os_error()) }
-    }
-}
diff --git a/library/std/src/sys/pal/unsupported/common.rs b/library/std/src/sys/pal/unsupported/common.rs
index 76f80291f0ea8..34a766683830d 100644
--- a/library/std/src/sys/pal/unsupported/common.rs
+++ b/library/std/src/sys/pal/unsupported/common.rs
@@ -27,7 +27,3 @@ pub fn decode_error_kind(_code: i32) -> crate::io::ErrorKind {
 pub fn abort_internal() -> ! {
     core::intrinsics::abort();
 }
-
-pub fn hashmap_random_keys() -> (u64, u64) {
-    (1, 2)
-}
diff --git a/library/std/src/sys/pal/wasi/helpers.rs b/library/std/src/sys/pal/wasi/helpers.rs
index 37ef17858cb96..404747f0dc756 100644
--- a/library/std/src/sys/pal/wasi/helpers.rs
+++ b/library/std/src/sys/pal/wasi/helpers.rs
@@ -1,6 +1,6 @@
 #![forbid(unsafe_op_in_unsafe_fn)]
 
-use crate::{io as std_io, mem};
+use crate::io as std_io;
 
 #[inline]
 pub fn is_interrupted(errno: i32) -> bool {
@@ -108,16 +108,6 @@ pub fn abort_internal() -> ! {
     unsafe { libc::abort() }
 }
 
-pub fn hashmap_random_keys() -> (u64, u64) {
-    let mut ret = (0u64, 0u64);
-    unsafe {
-        let base = &mut ret as *mut (u64, u64) as *mut u8;
-        let len = mem::size_of_val(&ret);
-        wasi::random_get(base, len).expect("random_get failure");
-    }
-    ret
-}
-
 #[inline]
 pub(crate) fn err2io(err: wasi::Errno) -> std_io::Error {
     std_io::Error::from_raw_os_error(err.raw().into())
diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs
index 8051021a58897..5d54c7903065c 100644
--- a/library/std/src/sys/pal/wasi/mod.rs
+++ b/library/std/src/sys/pal/wasi/mod.rs
@@ -47,4 +47,4 @@ mod helpers;
 // then the compiler complains about conflicts.
 
 use helpers::err2io;
-pub use helpers::{abort_internal, decode_error_kind, hashmap_random_keys, is_interrupted};
+pub use helpers::{abort_internal, decode_error_kind, is_interrupted};
diff --git a/library/std/src/sys/pal/wasip2/mod.rs b/library/std/src/sys/pal/wasip2/mod.rs
index 546fadbe5011c..17b26543bd75f 100644
--- a/library/std/src/sys/pal/wasip2/mod.rs
+++ b/library/std/src/sys/pal/wasip2/mod.rs
@@ -50,6 +50,6 @@ mod helpers;
 // then the compiler complains about conflicts.
 
 use helpers::err2io;
-pub use helpers::{abort_internal, decode_error_kind, hashmap_random_keys, is_interrupted};
+pub use helpers::{abort_internal, decode_error_kind, is_interrupted};
 
 mod cabi_realloc;
diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs
index f5ed3e4628e1f..1ea253e5e5263 100644
--- a/library/std/src/sys/pal/windows/mod.rs
+++ b/library/std/src/sys/pal/windows/mod.rs
@@ -1,7 +1,6 @@
 #![allow(missing_docs, nonstandard_style)]
 #![forbid(unsafe_op_in_unsafe_fn)]
 
-pub use self::rand::hashmap_random_keys;
 use crate::ffi::{OsStr, OsString};
 use crate::io::ErrorKind;
 use crate::mem::MaybeUninit;
@@ -27,7 +26,6 @@ pub mod net;
 pub mod os;
 pub mod pipe;
 pub mod process;
-pub mod rand;
 pub mod stdio;
 pub mod thread;
 pub mod time;
diff --git a/library/std/src/sys/pal/windows/pipe.rs b/library/std/src/sys/pal/windows/pipe.rs
index 7d1b5aca1d5fe..d8200ef9ca460 100644
--- a/library/std/src/sys/pal/windows/pipe.rs
+++ b/library/std/src/sys/pal/windows/pipe.rs
@@ -2,12 +2,13 @@ use crate::ffi::OsStr;
 use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
 use crate::os::windows::prelude::*;
 use crate::path::Path;
+use crate::random::{DefaultRandomSource, Random};
 use crate::sync::atomic::AtomicUsize;
 use crate::sync::atomic::Ordering::Relaxed;
+use crate::sys::c;
 use crate::sys::fs::{File, OpenOptions};
 use crate::sys::handle::Handle;
 use crate::sys::pal::windows::api::{self, WinError};
-use crate::sys::{c, hashmap_random_keys};
 use crate::sys_common::{FromInner, IntoInner};
 use crate::{mem, ptr};
 
@@ -79,7 +80,7 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res
             name = format!(
                 r"\\.\pipe\__rust_anonymous_pipe1__.{}.{}",
                 c::GetCurrentProcessId(),
-                random_number()
+                random_number(),
             );
             let wide_name = OsStr::new(&name).encode_wide().chain(Some(0)).collect::<Vec<_>>();
             let mut flags = c::FILE_FLAG_FIRST_PIPE_INSTANCE | c::FILE_FLAG_OVERLAPPED;
@@ -214,7 +215,7 @@ fn random_number() -> usize {
             return N.fetch_add(1, Relaxed);
         }
 
-        N.store(hashmap_random_keys().0 as usize, Relaxed);
+        N.store(usize::random(&mut DefaultRandomSource), Relaxed);
     }
 }
 
diff --git a/library/std/src/sys/pal/windows/rand.rs b/library/std/src/sys/pal/windows/rand.rs
deleted file mode 100644
index e366bb995626a..0000000000000
--- a/library/std/src/sys/pal/windows/rand.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-use core::{mem, ptr};
-
-use crate::sys::c;
-
-#[cfg(not(target_vendor = "win7"))]
-#[inline]
-pub fn hashmap_random_keys() -> (u64, u64) {
-    let mut v = (0, 0);
-    let ret = unsafe { c::ProcessPrng(ptr::addr_of_mut!(v).cast::<u8>(), mem::size_of_val(&v)) };
-    // ProcessPrng is documented as always returning `TRUE`.
-    // https://learn.microsoft.com/en-us/windows/win32/seccng/processprng#return-value
-    debug_assert_eq!(ret, c::TRUE);
-    v
-}
-
-#[cfg(target_vendor = "win7")]
-pub fn hashmap_random_keys() -> (u64, u64) {
-    use crate::ffi::c_void;
-    use crate::io;
-
-    let mut v = (0, 0);
-    let ret = unsafe {
-        c::RtlGenRandom(ptr::addr_of_mut!(v).cast::<c_void>(), mem::size_of_val(&v) as u32)
-    };
-
-    if ret != 0 { v } else { panic!("RNG broken: {}", io::Error::last_os_error()) }
-}
diff --git a/library/std/src/sys/pal/zkvm/mod.rs b/library/std/src/sys/pal/zkvm/mod.rs
index 20fdb7468a40d..6ea057720296d 100644
--- a/library/std/src/sys/pal/zkvm/mod.rs
+++ b/library/std/src/sys/pal/zkvm/mod.rs
@@ -60,11 +60,3 @@ pub fn decode_error_kind(_code: i32) -> crate::io::ErrorKind {
 pub fn abort_internal() -> ! {
     core::intrinsics::abort();
 }
-
-pub fn hashmap_random_keys() -> (u64, u64) {
-    let mut buf = [0u32; 4];
-    unsafe {
-        abi::sys_rand(buf.as_mut_ptr(), 4);
-    };
-    ((buf[0] as u64) << 32 + buf[1] as u64, (buf[2] as u64) << 32 + buf[3] as u64)
-}
diff --git a/library/std/src/sys/random/apple.rs b/library/std/src/sys/random/apple.rs
new file mode 100644
index 0000000000000..417198c9d850a
--- /dev/null
+++ b/library/std/src/sys/random/apple.rs
@@ -0,0 +1,15 @@
+//! Random data on Apple platforms.
+//!
+//! `CCRandomGenerateBytes` calls into `CCRandomCopyBytes` with `kCCRandomDefault`.
+//! `CCRandomCopyBytes` manages a CSPRNG which is seeded from the kernel's CSPRNG.
+//! We use `CCRandomGenerateBytes` instead of `SecCopyBytes` because it is accessible via
+//! `libSystem` (libc) while the other needs to link to `Security.framework`.
+//!
+//! Note that technically, `arc4random_buf` is available as well, but that calls
+//! into the same system service anyway, and `CCRandomGenerateBytes` has been
+//! proven to be App Store-compatible.
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let ret = unsafe { libc::CCRandomGenerateBytes(bytes.as_mut_ptr().cast(), bytes.len()) };
+    assert_eq!(ret, libc::kCCSuccess, "failed to generate random data");
+}
diff --git a/library/std/src/sys/random/arc4random.rs b/library/std/src/sys/random/arc4random.rs
new file mode 100644
index 0000000000000..32467e9ebaa64
--- /dev/null
+++ b/library/std/src/sys/random/arc4random.rs
@@ -0,0 +1,34 @@
+//! Random data generation with `arc4random_buf`.
+//!
+//! Contrary to its name, `arc4random` doesn't actually use the horribly-broken
+//! RC4 cypher anymore, at least not on modern systems, but rather something
+//! like ChaCha20 with continual reseeding from the OS. That makes it an ideal
+//! source of large quantities of cryptographically secure data, which is exactly
+//! what we need for `DefaultRandomSource`. Unfortunately, it's not available
+//! on all UNIX systems, most notably Linux (until recently, but it's just a
+//! wrapper for `getrandom`. Since we need to hook into `getrandom` directly
+//! for `HashMap` keys anyway, we just keep our version).
+
+#[cfg(not(any(
+    target_os = "haiku",
+    target_os = "illumos",
+    target_os = "solaris",
+    target_os = "vita",
+)))]
+use libc::arc4random_buf;
+
+// FIXME: move these to libc (except Haiku, that one needs to link to libbsd.so).
+#[cfg(any(
+    target_os = "haiku", // See https://git.haiku-os.org/haiku/tree/headers/compatibility/bsd/stdlib.h
+    target_os = "illumos", // See https://www.illumos.org/man/3C/arc4random
+    target_os = "solaris", // See https://docs.oracle.com/cd/E88353_01/html/E37843/arc4random-3c.html
+    target_os = "vita", // See https://github.com/vitasdk/newlib/blob/b89e5bc183b516945f9ee07eef483ecb916e45ff/newlib/libc/include/stdlib.h#L74
+))]
+#[cfg_attr(target_os = "haiku", link(name = "bsd"))]
+extern "C" {
+    fn arc4random_buf(buf: *mut core::ffi::c_void, nbytes: libc::size_t);
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe { arc4random_buf(bytes.as_mut_ptr().cast(), bytes.len()) }
+}
diff --git a/library/std/src/sys/random/espidf.rs b/library/std/src/sys/random/espidf.rs
new file mode 100644
index 0000000000000..fd52cb5559ce5
--- /dev/null
+++ b/library/std/src/sys/random/espidf.rs
@@ -0,0 +1,9 @@
+use crate::ffi::c_void;
+
+extern "C" {
+    fn esp_fill_random(buf: *mut c_void, len: usize);
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe { esp_fill_random(bytes.as_mut_ptr().cast(), bytes.len()) }
+}
diff --git a/library/std/src/sys/random/fuchsia.rs b/library/std/src/sys/random/fuchsia.rs
new file mode 100644
index 0000000000000..77d72b3c5b784
--- /dev/null
+++ b/library/std/src/sys/random/fuchsia.rs
@@ -0,0 +1,13 @@
+//! Random data generation using the Zircon kernel.
+//!
+//! Fuchsia, as always, is quite nice and provides exactly the API we need:
+//! <https://fuchsia.dev/reference/syscalls/cprng_draw>.
+
+#[link(name = "zircon")]
+extern "C" {
+    fn zx_cprng_draw(buffer: *mut u8, len: usize);
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe { zx_cprng_draw(bytes.as_mut_ptr(), bytes.len()) }
+}
diff --git a/library/std/src/sys/random/getentropy.rs b/library/std/src/sys/random/getentropy.rs
new file mode 100644
index 0000000000000..110ac134c1f47
--- /dev/null
+++ b/library/std/src/sys/random/getentropy.rs
@@ -0,0 +1,17 @@
+//! Random data generation through `getentropy`.
+//!
+//! Since issue 8 (2024), the POSIX specification mandates the existence of the
+//! `getentropy` function, which fills a slice of up to `GETENTROPY_MAX` bytes
+//! (256 on all known platforms) with random data. Unfortunately, it's only
+//! meant to be used to seed other CPRNGs, which we don't have, so we only use
+//! it where `arc4random_buf` and friends aren't available or secure (currently
+//! that's only the case on Emscripten).
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    // GETENTROPY_MAX isn't defined yet on most platforms, but it's mandated
+    // to be at least 256, so just use that as limit.
+    for chunk in bytes.chunks_mut(256) {
+        let r = unsafe { libc::getentropy(chunk.as_mut_ptr().cast(), chunk.len()) };
+        assert_ne!(r, -1, "failed to generate random data");
+    }
+}
diff --git a/library/std/src/sys/random/hermit.rs b/library/std/src/sys/random/hermit.rs
new file mode 100644
index 0000000000000..92c0550d2d584
--- /dev/null
+++ b/library/std/src/sys/random/hermit.rs
@@ -0,0 +1,7 @@
+pub fn fill_bytes(mut bytes: &mut [u8]) {
+    while !bytes.is_empty() {
+        let res = unsafe { hermit_abi::read_entropy(bytes.as_mut_ptr(), bytes.len(), 0) };
+        assert_ne!(res, -1, "failed to generate random data");
+        bytes = &mut bytes[res as usize..];
+    }
+}
diff --git a/library/std/src/sys/random/horizon.rs b/library/std/src/sys/random/horizon.rs
new file mode 100644
index 0000000000000..0be2eae20a727
--- /dev/null
+++ b/library/std/src/sys/random/horizon.rs
@@ -0,0 +1,7 @@
+pub fn fill_bytes(mut bytes: &mut [u8]) {
+    while !bytes.is_empty() {
+        let r = unsafe { libc::getrandom(bytes.as_mut_ptr().cast(), bytes.len(), 0) };
+        assert_ne!(r, -1, "failed to generate random data");
+        bytes = &mut bytes[r as usize..];
+    }
+}
diff --git a/library/std/src/sys/random/linux.rs b/library/std/src/sys/random/linux.rs
new file mode 100644
index 0000000000000..073fdc45e611e
--- /dev/null
+++ b/library/std/src/sys/random/linux.rs
@@ -0,0 +1,170 @@
+//! Random data generation with the Linux kernel.
+//!
+//! The first interface random data interface to be introduced on Linux were
+//! the `/dev/random` and `/dev/urandom` special files. As paths can become
+//! unreachable when inside a chroot and when the file descriptors are exhausted,
+//! this was not enough to provide userspace with a reliable source of randomness,
+//! so when the OpenBSD 5.6 introduced the `getentropy` syscall, Linux 3.17 got
+//! its very own `getrandom`  syscall to match.[^1] Unfortunately, even if our
+//! minimum supported version were high enough, we still couldn't rely on the
+//! syscall being available, as it is blocked in `seccomp` by default.
+//!
+//! The question is therefore which of the random sources to use. Historically,
+//! the kernel contained two pools: the blocking and non-blocking pool. The
+//! blocking pool used entropy estimation to limit the amount of available
+//! bytes, while the non-blocking pool, once initialized using the blocking
+//! pool, uses a CPRNG to return an unlimited number of random bytes. With a
+//! strong enough CPRNG however, the entropy estimation didn't contribute that
+//! much towards security while being an excellent vector for DoS attacs. Thus,
+//! the blocking pool was removed in kernel version 5.6.[^2] That patch did not
+//! magically increase the quality of the non-blocking pool, however, so we can
+//! safely consider it strong enough even in older kernel versions and use it
+//! unconditionally.
+//!
+//! One additional consideration to make is that the non-blocking pool is not
+//! always initialized during early boot. We want the best quality of randomness
+//! for the output of `DefaultRandomSource` so we simply wait until it is
+//! initialized. When `HashMap` keys however, this represents a potential source
+//! of deadlocks, as the additional entropy may only be generated once the
+//! program makes forward progress. In that case, we just use the best random
+//! data the system has available at the time.
+//!
+//! So in conclusion, we always want the output of the non-blocking pool, but
+//! may need to wait until it is initalized. The default behaviour of `getrandom`
+//! is to wait until the non-blocking pool is initialized and then draw from there,
+//! so if `getrandom` is available, we use its default to generate the bytes. For
+//! `HashMap`, however, we need to specify the `GRND_INSECURE` flags, but that
+//! is only available starting with kernel version 5.6. Thus, if we detect that
+//! the flag is unsupported, we try `GRND_NONBLOCK` instead, which will only
+//! succeed if the pool is initialized. If it isn't, we fall back to the file
+//! access method.
+//!
+//! The behaviour of `/dev/urandom` is inverse to that of `getrandom`: it always
+//! yields data, even when the pool is not initialized. For generating `HashMap`
+//! keys, this is not important, so we can use it directly. For secure data
+//! however, we need to wait until initialization, which we can do by `poll`ing
+//! `/dev/random`.
+//!
+//! TLDR: our fallback strategies are:
+//!
+//! Secure data                                 | `HashMap` keys
+//! --------------------------------------------|------------------
+//! getrandom(0)                                | getrandom(GRND_INSECURE)
+//! poll("/dev/random") && read("/dev/urandom") | getrandom(GRND_NONBLOCK)
+//!                                             | read("/dev/urandom")
+//!
+//! [^1]: <https://lwn.net/Articles/606141/>
+//! [^2]: <https://lwn.net/Articles/808575/>
+//!
+// FIXME(in 2040 or so): once the minimum kernel version is 5.6, remove the
+// `GRND_NONBLOCK` fallback and use `/dev/random` instead of `/dev/urandom`
+// when secure data is required.
+
+use crate::fs::File;
+use crate::io::Read;
+use crate::os::fd::AsRawFd;
+use crate::sync::OnceLock;
+use crate::sync::atomic::AtomicBool;
+use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
+use crate::sys::pal::os::errno;
+use crate::sys::pal::weak::syscall;
+
+fn getrandom(mut bytes: &mut [u8], insecure: bool) {
+    // A weak symbol allows interposition, e.g. for perf measurements that want to
+    // disable randomness for consistency. Otherwise, we'll try a raw syscall.
+    // (`getrandom` was added in glibc 2.25, musl 1.1.20, android API level 28)
+    syscall! {
+        fn getrandom(
+            buffer: *mut libc::c_void,
+            length: libc::size_t,
+            flags: libc::c_uint
+        ) -> libc::ssize_t
+    }
+
+    static GETRANDOM_AVAILABLE: AtomicBool = AtomicBool::new(true);
+    static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true);
+    static URANDOM_READY: AtomicBool = AtomicBool::new(false);
+    static DEVICE: OnceLock<File> = OnceLock::new();
+
+    if GETRANDOM_AVAILABLE.load(Relaxed) {
+        loop {
+            if bytes.is_empty() {
+                return;
+            }
+
+            let flags = if insecure {
+                if GRND_INSECURE_AVAILABLE.load(Relaxed) {
+                    libc::GRND_INSECURE
+                } else {
+                    libc::GRND_NONBLOCK
+                }
+            } else {
+                0
+            };
+
+            let ret = unsafe { getrandom(bytes.as_mut_ptr().cast(), bytes.len(), flags) };
+            if ret != -1 {
+                bytes = &mut bytes[ret as usize..];
+            } else {
+                match errno() {
+                    libc::EINTR => continue,
+                    // `GRND_INSECURE` is not available, try
+                    // `GRND_NONBLOCK`.
+                    libc::EINVAL if flags == libc::GRND_INSECURE => {
+                        GRND_INSECURE_AVAILABLE.store(false, Relaxed);
+                        continue;
+                    }
+                    // The pool is not initialized yet, fall back to
+                    // /dev/urandom for now.
+                    libc::EAGAIN if flags == libc::GRND_NONBLOCK => break,
+                    // `getrandom` is unavailable or blocked by seccomp.
+                    // Don't try it again and fall back to /dev/urandom.
+                    libc::ENOSYS | libc::EPERM => {
+                        GETRANDOM_AVAILABLE.store(false, Relaxed);
+                        break;
+                    }
+                    _ => panic!("failed to generate random data"),
+                }
+            }
+        }
+    }
+
+    // When we want cryptographic strength, we need to wait for the CPRNG-pool
+    // to become initialized. Do this by polling `/dev/random` until it is ready.
+    if !insecure {
+        if !URANDOM_READY.load(Acquire) {
+            let random = File::open("/dev/random").expect("failed to open /dev/random");
+            let mut fd = libc::pollfd { fd: random.as_raw_fd(), events: libc::POLLIN, revents: 0 };
+
+            while !URANDOM_READY.load(Acquire) {
+                let ret = unsafe { libc::poll(&mut fd, 1, -1) };
+                match ret {
+                    1 => {
+                        assert_eq!(fd.revents, libc::POLLIN);
+                        URANDOM_READY.store(true, Release);
+                        break;
+                    }
+                    -1 if errno() == libc::EINTR => continue,
+                    _ => panic!("poll(\"/dev/random\") failed"),
+                }
+            }
+        }
+    }
+
+    DEVICE
+        .get_or_try_init(|| File::open("/dev/urandom"))
+        .and_then(|mut dev| dev.read_exact(bytes))
+        .expect("failed to generate random data");
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    getrandom(bytes, false);
+}
+
+pub fn hashmap_random_keys() -> (u64, u64) {
+    let mut bytes = [0; 16];
+    getrandom(&mut bytes, true);
+    let k1 = u64::from_ne_bytes(bytes[..8].try_into().unwrap());
+    let k2 = u64::from_ne_bytes(bytes[8..].try_into().unwrap());
+    (k1, k2)
+}
diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs
new file mode 100644
index 0000000000000..16fb8c64c9b8f
--- /dev/null
+++ b/library/std/src/sys/random/mod.rs
@@ -0,0 +1,95 @@
+cfg_if::cfg_if! {
+    // Tier 1
+    if #[cfg(any(target_os = "linux", target_os = "android"))] {
+        mod linux;
+        pub use linux::{fill_bytes, hashmap_random_keys};
+    } else if #[cfg(target_os = "windows")] {
+        mod windows;
+        pub use windows::fill_bytes;
+    } else if #[cfg(target_vendor = "apple")] {
+        mod apple;
+        pub use apple::fill_bytes;
+    // Others, in alphabetical ordering.
+    } else if #[cfg(any(
+        target_os = "dragonfly",
+        target_os = "freebsd",
+        target_os = "haiku",
+        target_os = "illumos",
+        target_os = "netbsd",
+        target_os = "openbsd",
+        target_os = "solaris",
+        target_os = "vita",
+    ))] {
+        mod arc4random;
+        pub use arc4random::fill_bytes;
+    } else if #[cfg(target_os = "emscripten")] {
+        mod getentropy;
+        pub use getentropy::fill_bytes;
+    } else if #[cfg(target_os = "espidf")] {
+        mod espidf;
+        pub use espidf::fill_bytes;
+    } else if #[cfg(target_os = "fuchsia")] {
+        mod fuchsia;
+        pub use fuchsia::fill_bytes;
+    } else if #[cfg(target_os = "hermit")] {
+        mod hermit;
+        pub use hermit::fill_bytes;
+    } else if #[cfg(target_os = "horizon")] {
+        // FIXME: add arc4random_buf to shim-3ds
+        mod horizon;
+        pub use horizon::fill_bytes;
+    } else if #[cfg(any(
+        target_os = "hurd",
+        target_os = "l4re",
+        target_os = "nto",
+    ))] {
+        mod unix_legacy;
+        pub use unix_legacy::fill_bytes;
+    } else if #[cfg(target_os = "redox")] {
+        mod redox;
+        pub use redox::fill_bytes;
+    } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
+        mod sgx;
+        pub use sgx::fill_bytes;
+    } else if #[cfg(target_os = "solid_asp3")] {
+        mod solid;
+        pub use solid::fill_bytes;
+    } else if #[cfg(target_os = "teeos")] {
+        mod teeos;
+        pub use teeos::fill_bytes;
+    } else if #[cfg(target_os = "uefi")] {
+        mod uefi;
+        pub use uefi::fill_bytes;
+    } else if #[cfg(target_os = "vxworks")] {
+        mod vxworks;
+        pub use vxworks::fill_bytes;
+    } else if #[cfg(target_os = "wasi")] {
+        mod wasi;
+        pub use wasi::fill_bytes;
+    } else if #[cfg(target_os = "zkvm")] {
+        mod zkvm;
+        pub use zkvm::fill_bytes;
+    } else if #[cfg(any(
+        all(target_family = "wasm", target_os = "unknown"),
+        target_os = "xous",
+    ))] {
+        // FIXME: finally remove std support for wasm32-unknown-unknown
+        // FIXME: add random data generation to xous
+        mod unsupported;
+        pub use unsupported::{fill_bytes, hashmap_random_keys};
+    }
+}
+
+#[cfg(not(any(
+    target_os = "linux",
+    target_os = "android",
+    all(target_family = "wasm", target_os = "unknown"),
+    target_os = "xous",
+)))]
+pub fn hashmap_random_keys() -> (u64, u64) {
+    let mut buf = [0; 16];
+    fill_bytes(&mut buf);
+    let k1 = u64::from_ne_bytes(buf[..8].try_into().unwrap());
+    let k2 = u64::from_ne_bytes(buf[8..].try_into().unwrap());
+    (k1, k2)
+}
diff --git a/library/std/src/sys/random/redox.rs b/library/std/src/sys/random/redox.rs
new file mode 100644
index 0000000000000..b004335a35176
--- /dev/null
+++ b/library/std/src/sys/random/redox.rs
@@ -0,0 +1,12 @@
+use crate::fs::File;
+use crate::io::Read;
+use crate::sync::OnceLock;
+
+static SCHEME: OnceLock<File> = OnceLock::new();
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    SCHEME
+        .get_or_try_init(|| File::open("/scheme/rand"))
+        .and_then(|mut scheme| scheme.read_exact(bytes))
+        .expect("failed to generate random data");
+}
diff --git a/library/std/src/sys/random/sgx.rs b/library/std/src/sys/random/sgx.rs
new file mode 100644
index 0000000000000..c3647a8df220e
--- /dev/null
+++ b/library/std/src/sys/random/sgx.rs
@@ -0,0 +1,67 @@
+use crate::arch::x86_64::{_rdrand16_step, _rdrand32_step, _rdrand64_step};
+
+const RETRIES: u32 = 10;
+
+fn fail() -> ! {
+    panic!("failed to generate random data");
+}
+
+fn rdrand64() -> u64 {
+    unsafe {
+        let mut ret: u64 = 0;
+        for _ in 0..RETRIES {
+            if _rdrand64_step(&mut ret) == 1 {
+                return ret;
+            }
+        }
+
+        fail();
+    }
+}
+
+fn rdrand32() -> u32 {
+    unsafe {
+        let mut ret: u32 = 0;
+        for _ in 0..RETRIES {
+            if _rdrand32_step(&mut ret) == 1 {
+                return ret;
+            }
+        }
+
+        fail();
+    }
+}
+
+fn rdrand16() -> u16 {
+    unsafe {
+        let mut ret: u16 = 0;
+        for _ in 0..RETRIES {
+            if _rdrand16_step(&mut ret) == 1 {
+                return ret;
+            }
+        }
+
+        fail();
+    }
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let mut chunks = bytes.array_chunks_mut();
+    for chunk in &mut chunks {
+        *chunk = rdrand64().to_ne_bytes();
+    }
+
+    let mut chunks = chunks.into_remainder().array_chunks_mut();
+    for chunk in &mut chunks {
+        *chunk = rdrand32().to_ne_bytes();
+    }
+
+    let mut chunks = chunks.into_remainder().array_chunks_mut();
+    for chunk in &mut chunks {
+        *chunk = rdrand16().to_ne_bytes();
+    }
+
+    if let [byte] = chunks.into_remainder() {
+        *byte = rdrand16() as u8;
+    }
+}
diff --git a/library/std/src/sys/random/solid.rs b/library/std/src/sys/random/solid.rs
new file mode 100644
index 0000000000000..545771150e284
--- /dev/null
+++ b/library/std/src/sys/random/solid.rs
@@ -0,0 +1,8 @@
+use crate::sys::pal::abi;
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe {
+        let result = abi::SOLID_RNG_SampleRandomBytes(bytes.as_mut_ptr(), bytes.len());
+        assert_eq!(result, 0, "failed to generate random data");
+    }
+}
diff --git a/library/std/src/sys/random/teeos.rs b/library/std/src/sys/random/teeos.rs
new file mode 100644
index 0000000000000..fd6b24e19e982
--- /dev/null
+++ b/library/std/src/sys/random/teeos.rs
@@ -0,0 +1,7 @@
+extern "C" {
+    fn TEE_GenerateRandom(randomBuffer: *mut core::ffi::c_void, randomBufferLen: libc::size_t);
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe { TEE_GenerateRandom(bytes.as_mut_ptr().cast(), bytes.len()) }
+}
diff --git a/library/std/src/sys/random/uefi.rs b/library/std/src/sys/random/uefi.rs
new file mode 100644
index 0000000000000..a4d29e66f3875
--- /dev/null
+++ b/library/std/src/sys/random/uefi.rs
@@ -0,0 +1,27 @@
+use r_efi::protocols::rng;
+
+use crate::sys::pal::helpers;
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let handles =
+        helpers::locate_handles(rng::PROTOCOL_GUID).expect("failed to generate random data");
+    for handle in handles {
+        if let Ok(protocol) = helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
+            let r = unsafe {
+                ((*protocol.as_ptr()).get_rng)(
+                    protocol.as_ptr(),
+                    crate::ptr::null_mut(),
+                    bytes.len(),
+                    bytes.as_mut_ptr(),
+                )
+            };
+            if r.is_error() {
+                continue;
+            } else {
+                return;
+            }
+        }
+    }
+
+    panic!("failed to generate random data");
+}
diff --git a/library/std/src/sys/random/unix_legacy.rs b/library/std/src/sys/random/unix_legacy.rs
new file mode 100644
index 0000000000000..587068b0d6641
--- /dev/null
+++ b/library/std/src/sys/random/unix_legacy.rs
@@ -0,0 +1,20 @@
+//! Random data from `/dev/urandom`
+//!
+//! Before `getentropy` was standardized in 2024, UNIX didn't have a standardized
+//! way of getting random data, so systems just followed the precedent set by
+//! Linux and exposed random devices at `/dev/random` and `/dev/urandom`. Thus,
+//! for the few systems that support neither `arc4random_buf` nor `getentropy`
+//! yet, we just read from the file.
+
+use crate::fs::File;
+use crate::io::Read;
+use crate::sync::OnceLock;
+
+static DEVICE: OnceLock<File> = OnceLock::new();
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    DEVICE
+        .get_or_try_init(|| File::open("/dev/urandom"))
+        .and_then(|mut dev| dev.read_exact(bytes))
+        .expect("failed to generate random data");
+}
diff --git a/library/std/src/sys/random/unsupported.rs b/library/std/src/sys/random/unsupported.rs
new file mode 100644
index 0000000000000..d68ce4a9e8703
--- /dev/null
+++ b/library/std/src/sys/random/unsupported.rs
@@ -0,0 +1,15 @@
+use crate::ptr;
+
+pub fn fill_bytes(_: &mut [u8]) {
+    panic!("this target does not support random data generation");
+}
+
+pub fn hashmap_random_keys() -> (u64, u64) {
+    // Use allocation addresses for a bit of randomness. This isn't
+    // particularily secure, but there isn't really an alternative.
+    let stack = 0u8;
+    let heap = Box::new(0u8);
+    let k1 = ptr::from_ref(&stack).addr() as u64;
+    let k2 = ptr::from_ref(&*heap).addr() as u64;
+    (k1, k2)
+}
diff --git a/library/std/src/sys/random/vxworks.rs b/library/std/src/sys/random/vxworks.rs
new file mode 100644
index 0000000000000..d549ccebdb2cd
--- /dev/null
+++ b/library/std/src/sys/random/vxworks.rs
@@ -0,0 +1,25 @@
+use crate::sync::atomic::AtomicBool;
+use crate::sync::atomic::Ordering::Relaxed;
+
+static RNG_INIT: AtomicBool = AtomicBool::new(false);
+
+pub fn fill_bytes(mut bytes: &mut [u8]) {
+    while !RNG_INIT.load(Relaxed) {
+        let ret = unsafe { libc::randSecure() };
+        if ret < 0 {
+            panic!("failed to generate random data");
+        } else if ret > 0 {
+            RNG_INIT.store(true, Relaxed);
+            break;
+        }
+
+        unsafe { libc::usleep(10) };
+    }
+
+    while !bytes.is_empty() {
+        let len = bytes.len().try_into().unwrap_or(libc::c_int::MAX);
+        let ret = unsafe { libc::randABytes(bytes.as_mut_ptr(), len) };
+        assert!(ret >= 0, "failed to generate random data");
+        bytes = &mut bytes[len as usize..];
+    }
+}
diff --git a/library/std/src/sys/random/wasi.rs b/library/std/src/sys/random/wasi.rs
new file mode 100644
index 0000000000000..d41da3751fc04
--- /dev/null
+++ b/library/std/src/sys/random/wasi.rs
@@ -0,0 +1,5 @@
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe {
+        wasi::random_get(bytes.as_mut_ptr(), bytes.len()).expect("failed to generate random data")
+    }
+}
diff --git a/library/std/src/sys/random/windows.rs b/library/std/src/sys/random/windows.rs
new file mode 100644
index 0000000000000..7566000f9e6ff
--- /dev/null
+++ b/library/std/src/sys/random/windows.rs
@@ -0,0 +1,20 @@
+use crate::sys::c;
+
+#[cfg(not(target_vendor = "win7"))]
+#[inline]
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let ret = unsafe { c::ProcessPrng(bytes.as_mut_ptr(), bytes.len()) };
+    // ProcessPrng is documented as always returning `TRUE`.
+    // https://learn.microsoft.com/en-us/windows/win32/seccng/processprng#return-value
+    debug_assert_eq!(ret, c::TRUE);
+}
+
+#[cfg(target_vendor = "win7")]
+pub fn fill_bytes(mut bytes: &mut [u8]) {
+    while !bytes.is_empty() {
+        let len = bytes.len().try_into().unwrap_or(u32::MAX);
+        let ret = unsafe { c::RtlGenRandom(bytes.as_mut_ptr().cast(), len) };
+        assert_ne!(ret, 0, "failed to generate random data");
+        bytes = &mut bytes[len as usize..];
+    }
+}
diff --git a/library/std/src/sys/random/zkvm.rs b/library/std/src/sys/random/zkvm.rs
new file mode 100644
index 0000000000000..3011942f6b26b
--- /dev/null
+++ b/library/std/src/sys/random/zkvm.rs
@@ -0,0 +1,21 @@
+use crate::sys::pal::abi;
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let (pre, words, post) = unsafe { bytes.align_to_mut::<u32>() };
+    if !words.is_empty() {
+        unsafe {
+            abi::sys_rand(words.as_mut_ptr(), words.len());
+        }
+    }
+
+    let mut buf = [0u32; 2];
+    let len = (pre.len() + post.len() + size_of::<u32>() - 1) / size_of::<u32>();
+    if len != 0 {
+        unsafe { abi::sys_rand(buf.as_mut_ptr(), len) };
+    }
+
+    let buf = buf.map(u32::to_ne_bytes);
+    let buf = buf.as_flattened();
+    pre.copy_from_slice(&buf[..pre.len()]);
+    post.copy_from_slice(&buf[pre.len()..pre.len() + post.len()]);
+}
diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs
index 919a42180d751..3d504c3771fb2 100644
--- a/src/bootstrap/src/core/build_steps/doc.rs
+++ b/src/bootstrap/src/core/build_steps/doc.rs
@@ -63,7 +63,7 @@ macro_rules! book {
                     src: builder.src.join($path),
                     parent: Some(self),
                     languages: $lang.into(),
-                    rustdoc: None,
+                    rustdoc_compiler: None,
                 })
             }
         }
@@ -113,7 +113,7 @@ impl Step for UnstableBook {
             src: builder.md_doc_out(self.target).join("unstable-book"),
             parent: Some(self),
             languages: vec![],
-            rustdoc: None,
+            rustdoc_compiler: None,
         })
     }
 }
@@ -125,7 +125,7 @@ struct RustbookSrc<P: Step> {
     src: PathBuf,
     parent: Option<P>,
     languages: Vec<&'static str>,
-    rustdoc: Option<PathBuf>,
+    rustdoc_compiler: Option<Compiler>,
 }
 
 impl<P: Step> Step for RustbookSrc<P> {
@@ -157,7 +157,9 @@ impl<P: Step> Step for RustbookSrc<P> {
             let _ = fs::remove_dir_all(&out);
 
             let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
-            if let Some(mut rustdoc) = self.rustdoc {
+
+            if let Some(compiler) = self.rustdoc_compiler {
+                let mut rustdoc = builder.rustdoc(compiler);
                 rustdoc.pop();
                 let old_path = env::var_os("PATH").unwrap_or_default();
                 let new_path =
@@ -165,6 +167,7 @@ impl<P: Step> Step for RustbookSrc<P> {
                         .expect("could not add rustdoc to PATH");
 
                 rustbook_cmd.env("PATH", new_path);
+                builder.add_rustc_lib_path(compiler, &mut rustbook_cmd);
             }
 
             rustbook_cmd.arg("build").arg(&src).arg("-d").arg(&out).run(builder);
@@ -240,7 +243,7 @@ impl Step for TheBook {
             src: absolute_path.clone(),
             parent: Some(self),
             languages: vec![],
-            rustdoc: None,
+            rustdoc_compiler: None,
         });
 
         // building older edition redirects
@@ -253,7 +256,7 @@ impl Step for TheBook {
                 // treat the other editions as not having a parent.
                 parent: Option::<Self>::None,
                 languages: vec![],
-                rustdoc: None,
+                rustdoc_compiler: None,
             });
         }
 
@@ -1236,7 +1239,7 @@ impl Step for RustcBook {
             src: out_base,
             parent: Some(self),
             languages: vec![],
-            rustdoc: None,
+            rustdoc_compiler: None,
         });
     }
 }
@@ -1270,16 +1273,15 @@ impl Step for Reference {
         // This is needed for generating links to the standard library using
         // the mdbook-spec plugin.
         builder.ensure(compile::Std::new(self.compiler, builder.config.build));
-        let rustdoc = builder.rustdoc(self.compiler);
 
         // Run rustbook/mdbook to generate the HTML pages.
         builder.ensure(RustbookSrc {
             target: self.target,
             name: "reference".to_owned(),
             src: builder.src.join("src/doc/reference"),
+            rustdoc_compiler: Some(self.compiler),
             parent: Some(self),
             languages: vec![],
-            rustdoc: Some(rustdoc),
         });
     }
 }
diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md
index 827a7065f3e07..3728d1f5160d2 100644
--- a/src/doc/rustc/src/platform-support.md
+++ b/src/doc/rustc/src/platform-support.md
@@ -138,7 +138,6 @@ target | std | notes
 [`aarch64-apple-ios`](platform-support/apple-ios.md) | ✓ | ARM64 iOS
 [`aarch64-apple-ios-macabi`](platform-support/apple-ios-macabi.md) | ✓ | Mac Catalyst on ARM64
 [`aarch64-apple-ios-sim`](platform-support/apple-ios.md) | ✓ | Apple iOS Simulator on ARM64
-`aarch64-fuchsia` | ✓ | Alias for `aarch64-unknown-fuchsia`
 [`aarch64-unknown-fuchsia`](platform-support/fuchsia.md) | ✓ | ARM64 Fuchsia
 [`aarch64-linux-android`](platform-support/android.md) | ✓ | ARM64 Android
 [`aarch64-pc-windows-gnullvm`](platform-support/pc-windows-gnullvm.md) | ✓ | ARM64 MinGW (Windows 10+), LLVM ABI
@@ -199,7 +198,6 @@ target | std | notes
 [`x86_64-apple-ios`](platform-support/apple-ios.md) | ✓ | 64-bit x86 iOS
 [`x86_64-apple-ios-macabi`](platform-support/apple-ios-macabi.md) | ✓ | Mac Catalyst on x86_64
 [`x86_64-fortanix-unknown-sgx`](platform-support/x86_64-fortanix-unknown-sgx.md) | ✓ | [Fortanix ABI] for 64-bit Intel SGX
-`x86_64-fuchsia` | ✓ | Alias for `x86_64-unknown-fuchsia`
 [`x86_64-unknown-fuchsia`](platform-support/fuchsia.md) | ✓ | 64-bit x86 Fuchsia
 [`x86_64-linux-android`](platform-support/android.md) | ✓ | 64-bit x86 Android
 `x86_64-pc-solaris` | ✓ | 64-bit Solaris 11, illumos
diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
index 47b887dec9473..95a41752059dc 100644
--- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
@@ -78,6 +78,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_pointer(environ, dest)?;
             }
 
+            // Random data generation
+            "CCRandomGenerateBytes" => {
+                let [bytes, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let bytes = this.read_pointer(bytes)?;
+                let count = this.read_target_usize(count)?;
+                let success = this.eval_libc_i32("kCCSuccess");
+                this.gen_random(bytes, count)?;
+                this.write_int(success, dest)?;
+            }
+
             // Time related shims
             "mach_absolute_time" => {
                 let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
diff --git a/src/tools/miri/tests/pass-dep/libc/ccrandomgeneratebytes_apple.rs b/src/tools/miri/tests/pass-dep/libc/ccrandomgeneratebytes_apple.rs
new file mode 100644
index 0000000000000..afcc02bc83971
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/libc/ccrandomgeneratebytes_apple.rs
@@ -0,0 +1,7 @@
+//@only-target: apple # This directly tests apple-only functions
+
+fn main() {
+    let mut bytes = [0u8; 24];
+    let ret = unsafe { libc::CCRandomGenerateBytes(bytes.as_mut_ptr().cast(), bytes.len()) };
+    assert_eq!(ret, libc::kCCSuccess);
+}
diff --git a/tests/assembly/targets/targets-elf.rs b/tests/assembly/targets/targets-elf.rs
index 4e1c5e6806e80..6ffa34f802e11 100644
--- a/tests/assembly/targets/targets-elf.rs
+++ b/tests/assembly/targets/targets-elf.rs
@@ -9,9 +9,6 @@
 //@ revisions: aarch64_be_unknown_netbsd
 //@ [aarch64_be_unknown_netbsd] compile-flags: --target aarch64_be-unknown-netbsd
 //@ [aarch64_be_unknown_netbsd] needs-llvm-components: aarch64
-//@ revisions: aarch64_fuchsia
-//@ [aarch64_fuchsia] compile-flags: --target aarch64-fuchsia
-//@ [aarch64_fuchsia] needs-llvm-components: aarch64
 //@ revisions: aarch64_kmc_solid_asp3
 //@ [aarch64_kmc_solid_asp3] compile-flags: --target aarch64-kmc-solid_asp3
 //@ [aarch64_kmc_solid_asp3] needs-llvm-components: aarch64
@@ -525,9 +522,6 @@
 //@ revisions: x86_64_fortanix_unknown_sgx
 //@ [x86_64_fortanix_unknown_sgx] compile-flags: --target x86_64-fortanix-unknown-sgx
 //@ [x86_64_fortanix_unknown_sgx] needs-llvm-components: x86
-//@ revisions: x86_64_fuchsia
-//@ [x86_64_fuchsia] compile-flags: --target x86_64-fuchsia
-//@ [x86_64_fuchsia] needs-llvm-components: x86
 //@ revisions: x86_64_linux_android
 //@ [x86_64_linux_android] compile-flags: --target x86_64-linux-android
 //@ [x86_64_linux_android] needs-llvm-components: x86
diff --git a/tests/ui/macros/break-last-token-twice.rs b/tests/ui/macros/break-last-token-twice.rs
new file mode 100644
index 0000000000000..791f349ab38df
--- /dev/null
+++ b/tests/ui/macros/break-last-token-twice.rs
@@ -0,0 +1,16 @@
+//@ check-pass
+
+macro_rules! m {
+    (static $name:ident: $t:ty = $e:expr) => {
+        let $name: $t = $e;
+    }
+}
+
+fn main() {
+    m! {
+        // Tricky: the trailing `>>=` token here is broken twice:
+        // - into `>` and `>=`
+        // - then the `>=` is broken into `>` and `=`
+        static _x: Vec<Vec<u32>>= vec![]
+    }
+}
diff --git a/tests/ui/parser/block-no-opening-brace.rs b/tests/ui/parser/block-no-opening-brace.rs
index e90a34104e8f5..2fde37ce6acef 100644
--- a/tests/ui/parser/block-no-opening-brace.rs
+++ b/tests/ui/parser/block-no-opening-brace.rs
@@ -4,28 +4,46 @@
 
 fn main() {}
 
-fn f1() {
+fn in_loop() {
     loop
         let x = 0; //~ ERROR expected `{`, found keyword `let`
         drop(0);
-    }
+}
 
-fn f2() {
+fn in_while() {
     while true
         let x = 0; //~ ERROR expected `{`, found keyword `let`
-    }
+}
 
-fn f3() {
+fn in_for() {
     for x in 0..1
         let x = 0; //~ ERROR expected `{`, found keyword `let`
-    }
+}
+
 
-fn f4() {
+// FIXME
+fn in_try() {
     try //~ ERROR expected expression, found reserved keyword `try`
         let x = 0;
-    }
+}
 
-fn f5() {
+// FIXME(#80931)
+fn in_async() {
     async
         let x = 0; //~ ERROR expected one of `move`, `|`, or `||`, found keyword `let`
+}
+
+// FIXME(#78168)
+fn in_const() {
+    let x = const 2; //~ ERROR expected expression, found keyword `const`
+}
+
+// FIXME(#78168)
+fn in_const_in_match() {
+    let x = 2;
+    match x {
+        const 2 => {}
+        //~^ ERROR expected identifier, found keyword `const`
+        //~| ERROR expected one of `=>`, `if`, or `|`, found `2`
     }
+}
diff --git a/tests/ui/parser/block-no-opening-brace.stderr b/tests/ui/parser/block-no-opening-brace.stderr
index f232f480ce9c2..83360944ed560 100644
--- a/tests/ui/parser/block-no-opening-brace.stderr
+++ b/tests/ui/parser/block-no-opening-brace.stderr
@@ -38,18 +38,36 @@ LL |         { let x = 0; }
    |         +            +
 
 error: expected expression, found reserved keyword `try`
-  --> $DIR/block-no-opening-brace.rs:24:5
+  --> $DIR/block-no-opening-brace.rs:26:5
    |
 LL |     try
    |     ^^^ expected expression
 
 error: expected one of `move`, `|`, or `||`, found keyword `let`
-  --> $DIR/block-no-opening-brace.rs:30:9
+  --> $DIR/block-no-opening-brace.rs:33:9
    |
 LL |     async
    |          - expected one of `move`, `|`, or `||`
 LL |         let x = 0;
    |         ^^^ unexpected token
 
-error: aborting due to 5 previous errors
+error: expected expression, found keyword `const`
+  --> $DIR/block-no-opening-brace.rs:38:13
+   |
+LL |     let x = const 2;
+   |             ^^^^^ expected expression
+
+error: expected identifier, found keyword `const`
+  --> $DIR/block-no-opening-brace.rs:45:9
+   |
+LL |         const 2 => {}
+   |         ^^^^^ expected identifier, found keyword
+
+error: expected one of `=>`, `if`, or `|`, found `2`
+  --> $DIR/block-no-opening-brace.rs:45:15
+   |
+LL |         const 2 => {}
+   |               ^ expected one of `=>`, `if`, or `|`
+
+error: aborting due to 8 previous errors
 
diff --git a/triagebot.toml b/triagebot.toml
index a6a0b02d7b710..5d80b9e656ea0 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -923,10 +923,8 @@ contributing_url = "https://rustc-dev-guide.rust-lang.org/getting-started.html"
 users_on_vacation = [
     "fmease",
     "jhpratt",
-    "joboet",
     "jyn514",
     "oli-obk",
-    "tgross35",
 ]
 
 [assign.adhoc_groups]