about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-01-18 18:40:20 +0000
committerbors <bors@rust-lang.org>2025-01-18 18:40:20 +0000
commit049355708383ab1b9a1046559b9d4230bdb3a5bc (patch)
treeea81d083222fd2702ba22948a314910e61e61872
parentefc25761e53fad5824f2085ae9caaccacca0879c (diff)
parent23fb4f22b3a72f4235b9b4fe2f29cbded981ef34 (diff)
downloadrust-049355708383ab1b9a1046559b9d4230bdb3a5bc.tar.gz
rust-049355708383ab1b9a1046559b9d4230bdb3a5bc.zip
Auto merge of #135682 - matthiaskrgr:rollup-cl7zlt1, r=matthiaskrgr
Rollup of 7 pull requests

Successful merges:

 - #133700 (const-eval: detect more pointers as definitely not-null)
 - #135290 (Encode constraints that hold at all points as logical edges in location-sensitive polonius)
 - #135478 (Run clippy for rustc_codegen_gcc on CI)
 - #135583 (Move `std::pipe::*` into `std::io`)
 - #135612 (Include x scripts in tarballs)
 - #135624 (ci: mirror buildkit image to ghcr)
 - #135661 (Stabilize `float_next_up_down`)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--.github/workflows/ghcr.yml23
-rw-r--r--compiler/rustc_borrowck/src/polonius/loan_liveness.rs83
-rw-r--r--compiler/rustc_borrowck/src/polonius/mod.rs1
-rw-r--r--compiler/rustc_borrowck/src/polonius/typeck_constraints.rs22
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs25
-rw-r--r--library/core/src/num/f128.rs10
-rw-r--r--library/core/src/num/f16.rs10
-rw-r--r--library/core/src/num/f32.rs14
-rw-r--r--library/core/src/num/f64.rs14
-rw-r--r--library/std/src/io/mod.rs249
-rw-r--r--library/std/src/io/tests.rs17
-rw-r--r--library/std/src/lib.rs3
-rw-r--r--library/std/src/pipe.rs258
-rw-r--r--library/std/src/pipe/tests.rs19
-rw-r--r--library/std/src/sys/anonymous_pipe/unix.rs3
-rw-r--r--library/std/src/sys/anonymous_pipe/unsupported.rs3
-rw-r--r--library/std/src/sys/anonymous_pipe/windows.rs4
-rw-r--r--library/std/src/sys/pal/unix/kernel_copy.rs5
-rw-r--r--library/std/tests/pipe_subprocess.rs3
-rw-r--r--src/bootstrap/src/core/build_steps/clippy.rs24
-rw-r--r--src/bootstrap/src/core/build_steps/dist.rs18
-rw-r--r--src/bootstrap/src/core/builder/mod.rs1
-rw-r--r--tests/run-make/broken-pipe-no-ice/rmake.rs2
-rw-r--r--tests/ui/consts/const-ptr-is-null.rs8
-rw-r--r--tests/ui/consts/const-ptr-is-null.stderr2
25 files changed, 444 insertions, 377 deletions
diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml
index 4793b28e722..052c9ae72b8 100644
--- a/.github/workflows/ghcr.yml
+++ b/.github/workflows/ghcr.yml
@@ -48,10 +48,21 @@ jobs:
 
       - name: Mirror DockerHub
         run: |
-          # DockerHub image we want to mirror
-          image="ubuntu:22.04"
+          # List of DockerHub images to mirror to ghcr.io
+          images=(
+            # Mirrored because used by the mingw-check-tidy, which doesn't cache Docker images
+            "ubuntu:22.04"
+            # Mirrored because used by all linux CI jobs, including mingw-check-tidy
+            "moby/buildkit:buildx-stable-1"
+          )
 
-          # Mirror image from DockerHub to ghcr.io
-          ./crane copy \
-            docker.io/${image} \
-            ghcr.io/${{ github.repository_owner }}/${image}
+          # Mirror each image from DockerHub to ghcr.io
+          for img in "${images[@]}"; do
+            echo "Mirroring ${img}..."
+            # Remove namespace from the image if any.
+            # E.g. "moby/buildkit:buildx-stable-1" becomes "buildkit:buildx-stable-1"
+            dest_image=$(echo "${img}" | cut -d'/' -f2-)
+            ./crane copy \
+              "docker.io/${img}" \
+              "ghcr.io/${{ github.repository_owner }}/${dest_image}"
+          done
diff --git a/compiler/rustc_borrowck/src/polonius/loan_liveness.rs b/compiler/rustc_borrowck/src/polonius/loan_liveness.rs
index c519453652f..768c12a97a6 100644
--- a/compiler/rustc_borrowck/src/polonius/loan_liveness.rs
+++ b/compiler/rustc_borrowck/src/polonius/loan_liveness.rs
@@ -9,16 +9,21 @@ use rustc_middle::ty::{RegionVid, TyCtxt};
 use rustc_mir_dataflow::points::PointIndex;
 
 use super::{LiveLoans, LocalizedOutlivesConstraintSet};
+use crate::constraints::OutlivesConstraint;
 use crate::dataflow::BorrowIndex;
 use crate::region_infer::values::LivenessValues;
+use crate::type_check::Locations;
 use crate::{BorrowSet, PlaceConflictBias, places_conflict};
 
-/// With the full graph of constraints, we can compute loan reachability, stop at kills, and trace
-/// loan liveness throughout the CFG.
+/// Compute loan reachability, stop at kills, and trace loan liveness throughout the CFG, by
+/// traversing the full graph of constraints that combines:
+/// - the localized constraints (the physical edges),
+/// - with the constraints that hold at all points (the logical edges).
 pub(super) fn compute_loan_liveness<'tcx>(
     tcx: TyCtxt<'tcx>,
     body: &Body<'tcx>,
     liveness: &LivenessValues,
+    outlives_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
     borrow_set: &BorrowSet<'tcx>,
     localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
 ) -> LiveLoans {
@@ -29,7 +34,11 @@ pub(super) fn compute_loan_liveness<'tcx>(
     // edges when visualizing the constraint graph anyways.
     let kills = collect_kills(body, tcx, borrow_set);
 
-    let graph = index_constraints(&localized_outlives_constraints);
+    // Create the full graph with the physical edges we've localized earlier, and the logical edges
+    // of constraints that hold at all points.
+    let logical_constraints =
+        outlives_constraints.filter(|c| matches!(c.locations, Locations::All(_)));
+    let graph = LocalizedConstraintGraph::new(&localized_outlives_constraints, logical_constraints);
     let mut visited = FxHashSet::default();
     let mut stack = Vec::new();
 
@@ -108,7 +117,7 @@ pub(super) fn compute_loan_liveness<'tcx>(
             let is_loan_killed =
                 kills.get(&current_location).is_some_and(|kills| kills.contains(&loan_idx));
 
-            for succ in outgoing_edges(&graph, node) {
+            for succ in graph.outgoing_edges(node) {
                 // If the loan is killed at this point, it is killed _on exit_. But only during
                 // forward traversal.
                 if is_loan_killed {
@@ -125,9 +134,17 @@ pub(super) fn compute_loan_liveness<'tcx>(
     live_loans
 }
 
-/// The localized constraint graph is currently the per-node map of its physical edges. In the
-/// future, we'll add logical edges to model constraints that hold at all points in the CFG.
-type LocalizedConstraintGraph = FxHashMap<LocalizedNode, FxIndexSet<LocalizedNode>>;
+/// The localized constraint graph indexes the physical and logical edges to compute a given node's
+/// successors during traversal.
+struct LocalizedConstraintGraph {
+    /// The actual, physical, edges we have recorded for a given node.
+    edges: FxHashMap<LocalizedNode, FxIndexSet<LocalizedNode>>,
+
+    /// The logical edges representing the outlives constraints that hold at all points in the CFG,
+    /// which we don't localize to avoid creating a lot of unnecessary edges in the graph. Some CFGs
+    /// can be big, and we don't need to create such a physical edge for every point in the CFG.
+    logical_edges: FxHashMap<RegionVid, FxIndexSet<RegionVid>>,
+}
 
 /// A node in the graph to be traversed, one of the two vertices of a localized outlives constraint.
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@@ -136,24 +153,44 @@ struct LocalizedNode {
     point: PointIndex,
 }
 
-/// Traverses the constraints and returns the indexable graph of edges per node.
-fn index_constraints(constraints: &LocalizedOutlivesConstraintSet) -> LocalizedConstraintGraph {
-    let mut edges = LocalizedConstraintGraph::default();
-    for constraint in &constraints.outlives {
-        let source = LocalizedNode { region: constraint.source, point: constraint.from };
-        let target = LocalizedNode { region: constraint.target, point: constraint.to };
-        edges.entry(source).or_default().insert(target);
-    }
+impl LocalizedConstraintGraph {
+    /// Traverses the constraints and returns the indexed graph of edges per node.
+    fn new<'tcx>(
+        constraints: &LocalizedOutlivesConstraintSet,
+        logical_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
+    ) -> Self {
+        let mut edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default();
+        for constraint in &constraints.outlives {
+            let source = LocalizedNode { region: constraint.source, point: constraint.from };
+            let target = LocalizedNode { region: constraint.target, point: constraint.to };
+            edges.entry(source).or_default().insert(target);
+        }
 
-    edges
-}
+        let mut logical_edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default();
+        for constraint in logical_constraints {
+            logical_edges.entry(constraint.sup).or_default().insert(constraint.sub);
+        }
+
+        LocalizedConstraintGraph { edges, logical_edges }
+    }
 
-/// Returns the outgoing edges of a given node, not its transitive closure.
-fn outgoing_edges(
-    graph: &LocalizedConstraintGraph,
-    node: LocalizedNode,
-) -> impl Iterator<Item = LocalizedNode> + use<'_> {
-    graph.get(&node).into_iter().flat_map(|edges| edges.iter().copied())
+    /// Returns the outgoing edges of a given node, not its transitive closure.
+    fn outgoing_edges(&self, node: LocalizedNode) -> impl Iterator<Item = LocalizedNode> + use<'_> {
+        // The outgoing edges are:
+        // - the physical edges present at this node,
+        // - the materialized logical edges that exist virtually at all points for this node's
+        //   region, localized at this point.
+        let physical_edges =
+            self.edges.get(&node).into_iter().flat_map(|targets| targets.iter().copied());
+        let materialized_edges =
+            self.logical_edges.get(&node.region).into_iter().flat_map(move |targets| {
+                targets
+                    .iter()
+                    .copied()
+                    .map(move |target| LocalizedNode { point: node.point, region: target })
+            });
+        physical_edges.chain(materialized_edges)
+    }
 }
 
 /// Traverses the MIR and collects kills.
diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs
index 52a5f75d8a2..502c868194a 100644
--- a/compiler/rustc_borrowck/src/polonius/mod.rs
+++ b/compiler/rustc_borrowck/src/polonius/mod.rs
@@ -130,6 +130,7 @@ impl PoloniusContext {
             tcx,
             body,
             regioncx.liveness_constraints(),
+            regioncx.outlives_constraints(),
             borrow_set,
             &localized_outlives_constraints,
         );
diff --git a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs
index 8235b844886..1289b1899eb 100644
--- a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs
+++ b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs
@@ -22,23 +22,11 @@ pub(super) fn convert_typeck_constraints<'tcx>(
     for outlives_constraint in outlives_constraints {
         match outlives_constraint.locations {
             Locations::All(_) => {
-                // For now, turn logical constraints holding at all points into physical edges at
-                // every point in the graph.
-                // FIXME: encode this into *traversal* instead.
-                for (block, bb) in body.basic_blocks.iter_enumerated() {
-                    let statement_count = bb.statements.len();
-                    for statement_index in 0..=statement_count {
-                        let current_location = Location { block, statement_index };
-                        let current_point = liveness.point_from_location(current_location);
-
-                        localized_outlives_constraints.push(LocalizedOutlivesConstraint {
-                            source: outlives_constraint.sup,
-                            from: current_point,
-                            target: outlives_constraint.sub,
-                            to: current_point,
-                        });
-                    }
-                }
+                // We don't turn constraints holding at all points into physical edges at every
+                // point in the graph. They are encoded into *traversal* instead: a given node's
+                // successors will combine these logical edges with the regular, physical, localized
+                // edges.
+                continue;
             }
 
             Locations::Single(location) => {
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index 0790db984e3..2772c94d52b 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -1481,22 +1481,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     /// Test if this value might be null.
     /// If the machine does not support ptr-to-int casts, this is conservative.
     pub fn scalar_may_be_null(&self, scalar: Scalar<M::Provenance>) -> InterpResult<'tcx, bool> {
-        interp_ok(match scalar.try_to_scalar_int() {
-            Ok(int) => int.is_null(),
+        match scalar.try_to_scalar_int() {
+            Ok(int) => interp_ok(int.is_null()),
             Err(_) => {
-                // Can only happen during CTFE.
+                // We can't cast this pointer to an integer. Can only happen during CTFE.
                 let ptr = scalar.to_pointer(self)?;
                 match self.ptr_try_get_alloc_id(ptr, 0) {
                     Ok((alloc_id, offset, _)) => {
-                        let size = self.get_alloc_info(alloc_id).size;
-                        // If the pointer is out-of-bounds, it may be null.
-                        // Note that one-past-the-end (offset == size) is still inbounds, and never null.
-                        offset > size
+                        let info = self.get_alloc_info(alloc_id);
+                        // If the pointer is in-bounds (including "at the end"), it is definitely not null.
+                        if offset <= info.size {
+                            return interp_ok(false);
+                        }
+                        // If the allocation is N-aligned, and the offset is not divisible by N,
+                        // then `base + offset` has a non-zero remainder after division by `N`,
+                        // which means `base + offset` cannot be null.
+                        if offset.bytes() % info.align.bytes() != 0 {
+                            return interp_ok(false);
+                        }
+                        // We don't know enough, this might be null.
+                        interp_ok(true)
                     }
                     Err(_offset) => bug!("a non-int scalar is always a pointer"),
                 }
             }
-        })
+        }
     }
 
     /// Turning a "maybe pointer" into a proper pointer (and some information
diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs
index 4ebeaf04611..8fb1588e60b 100644
--- a/library/core/src/num/f128.rs
+++ b/library/core/src/num/f128.rs
@@ -504,7 +504,6 @@ impl f128 {
     ///
     /// ```rust
     /// #![feature(f128)]
-    /// #![feature(float_next_up_down)]
     /// # // FIXME(f16_f128): remove when `eqtf2` is available
     /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
     ///
@@ -516,13 +515,15 @@ impl f128 {
     /// # }
     /// ```
     ///
+    /// This operation corresponds to IEEE-754 `nextUp`.
+    ///
     /// [`NEG_INFINITY`]: Self::NEG_INFINITY
     /// [`INFINITY`]: Self::INFINITY
     /// [`MIN`]: Self::MIN
     /// [`MAX`]: Self::MAX
     #[inline]
+    #[doc(alias = "nextUp")]
     #[unstable(feature = "f128", issue = "116909")]
-    // #[unstable(feature = "float_next_up_down", issue = "91399")]
     pub const fn next_up(self) -> Self {
         // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
         // denormals to zero. This is in general unsound and unsupported, but here
@@ -558,7 +559,6 @@ impl f128 {
     ///
     /// ```rust
     /// #![feature(f128)]
-    /// #![feature(float_next_up_down)]
     /// # // FIXME(f16_f128): remove when `eqtf2` is available
     /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
     ///
@@ -570,13 +570,15 @@ impl f128 {
     /// # }
     /// ```
     ///
+    /// This operation corresponds to IEEE-754 `nextDown`.
+    ///
     /// [`NEG_INFINITY`]: Self::NEG_INFINITY
     /// [`INFINITY`]: Self::INFINITY
     /// [`MIN`]: Self::MIN
     /// [`MAX`]: Self::MAX
     #[inline]
+    #[doc(alias = "nextDown")]
     #[unstable(feature = "f128", issue = "116909")]
-    // #[unstable(feature = "float_next_up_down", issue = "91399")]
     pub const fn next_down(self) -> Self {
         // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
         // denormals to zero. This is in general unsound and unsupported, but here
diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs
index c82f0d7cd4a..8c2af74b8f8 100644
--- a/library/core/src/num/f16.rs
+++ b/library/core/src/num/f16.rs
@@ -497,7 +497,6 @@ impl f16 {
     ///
     /// ```rust
     /// #![feature(f16)]
-    /// #![feature(float_next_up_down)]
     /// # // FIXME(f16_f128): ABI issues on MSVC
     /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
     ///
@@ -509,13 +508,15 @@ impl f16 {
     /// # }
     /// ```
     ///
+    /// This operation corresponds to IEEE-754 `nextUp`.
+    ///
     /// [`NEG_INFINITY`]: Self::NEG_INFINITY
     /// [`INFINITY`]: Self::INFINITY
     /// [`MIN`]: Self::MIN
     /// [`MAX`]: Self::MAX
     #[inline]
+    #[doc(alias = "nextUp")]
     #[unstable(feature = "f16", issue = "116909")]
-    // #[unstable(feature = "float_next_up_down", issue = "91399")]
     pub const fn next_up(self) -> Self {
         // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
         // denormals to zero. This is in general unsound and unsupported, but here
@@ -551,7 +552,6 @@ impl f16 {
     ///
     /// ```rust
     /// #![feature(f16)]
-    /// #![feature(float_next_up_down)]
     /// # // FIXME(f16_f128): ABI issues on MSVC
     /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
     ///
@@ -563,13 +563,15 @@ impl f16 {
     /// # }
     /// ```
     ///
+    /// This operation corresponds to IEEE-754 `nextDown`.
+    ///
     /// [`NEG_INFINITY`]: Self::NEG_INFINITY
     /// [`INFINITY`]: Self::INFINITY
     /// [`MIN`]: Self::MIN
     /// [`MAX`]: Self::MAX
     #[inline]
+    #[doc(alias = "nextDown")]
     #[unstable(feature = "f16", issue = "116909")]
-    // #[unstable(feature = "float_next_up_down", issue = "91399")]
     pub const fn next_down(self) -> Self {
         // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
         // denormals to zero. This is in general unsound and unsupported, but here
diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs
index 2b6adef65e9..817bedbd44f 100644
--- a/library/core/src/num/f32.rs
+++ b/library/core/src/num/f32.rs
@@ -726,7 +726,6 @@ impl f32 {
     /// is finite `x == x.next_up().next_down()` also holds.
     ///
     /// ```rust
-    /// #![feature(float_next_up_down)]
     /// // f32::EPSILON is the difference between 1.0 and the next number up.
     /// assert_eq!(1.0f32.next_up(), 1.0 + f32::EPSILON);
     /// // But not for most numbers.
@@ -734,12 +733,16 @@ impl f32 {
     /// assert_eq!(16777216f32.next_up(), 16777218.0);
     /// ```
     ///
+    /// This operation corresponds to IEEE-754 `nextUp`.
+    ///
     /// [`NEG_INFINITY`]: Self::NEG_INFINITY
     /// [`INFINITY`]: Self::INFINITY
     /// [`MIN`]: Self::MIN
     /// [`MAX`]: Self::MAX
     #[inline]
-    #[unstable(feature = "float_next_up_down", issue = "91399")]
+    #[doc(alias = "nextUp")]
+    #[stable(feature = "float_next_up_down", since = "CURRENT_RUSTC_VERSION")]
+    #[rustc_const_stable(feature = "float_next_up_down", since = "CURRENT_RUSTC_VERSION")]
     pub const fn next_up(self) -> Self {
         // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
         // denormals to zero. This is in general unsound and unsupported, but here
@@ -774,7 +777,6 @@ impl f32 {
     /// is finite `x == x.next_down().next_up()` also holds.
     ///
     /// ```rust
-    /// #![feature(float_next_up_down)]
     /// let x = 1.0f32;
     /// // Clamp value into range [0, 1).
     /// let clamped = x.clamp(0.0, 1.0f32.next_down());
@@ -782,12 +784,16 @@ impl f32 {
     /// assert_eq!(clamped.next_up(), 1.0);
     /// ```
     ///
+    /// This operation corresponds to IEEE-754 `nextDown`.
+    ///
     /// [`NEG_INFINITY`]: Self::NEG_INFINITY
     /// [`INFINITY`]: Self::INFINITY
     /// [`MIN`]: Self::MIN
     /// [`MAX`]: Self::MAX
     #[inline]
-    #[unstable(feature = "float_next_up_down", issue = "91399")]
+    #[doc(alias = "nextDown")]
+    #[stable(feature = "float_next_up_down", since = "CURRENT_RUSTC_VERSION")]
+    #[rustc_const_stable(feature = "float_next_up_down", since = "CURRENT_RUSTC_VERSION")]
     pub const fn next_down(self) -> Self {
         // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
         // denormals to zero. This is in general unsound and unsupported, but here
diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs
index 037b3afa6c4..1b0651a0def 100644
--- a/library/core/src/num/f64.rs
+++ b/library/core/src/num/f64.rs
@@ -743,7 +743,6 @@ impl f64 {
     /// is finite `x == x.next_up().next_down()` also holds.
     ///
     /// ```rust
-    /// #![feature(float_next_up_down)]
     /// // f64::EPSILON is the difference between 1.0 and the next number up.
     /// assert_eq!(1.0f64.next_up(), 1.0 + f64::EPSILON);
     /// // But not for most numbers.
@@ -751,12 +750,16 @@ impl f64 {
     /// assert_eq!(9007199254740992f64.next_up(), 9007199254740994.0);
     /// ```
     ///
+    /// This operation corresponds to IEEE-754 `nextUp`.
+    ///
     /// [`NEG_INFINITY`]: Self::NEG_INFINITY
     /// [`INFINITY`]: Self::INFINITY
     /// [`MIN`]: Self::MIN
     /// [`MAX`]: Self::MAX
     #[inline]
-    #[unstable(feature = "float_next_up_down", issue = "91399")]
+    #[doc(alias = "nextUp")]
+    #[stable(feature = "float_next_up_down", since = "CURRENT_RUSTC_VERSION")]
+    #[rustc_const_stable(feature = "float_next_up_down", since = "CURRENT_RUSTC_VERSION")]
     pub const fn next_up(self) -> Self {
         // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
         // denormals to zero. This is in general unsound and unsupported, but here
@@ -791,7 +794,6 @@ impl f64 {
     /// is finite `x == x.next_down().next_up()` also holds.
     ///
     /// ```rust
-    /// #![feature(float_next_up_down)]
     /// let x = 1.0f64;
     /// // Clamp value into range [0, 1).
     /// let clamped = x.clamp(0.0, 1.0f64.next_down());
@@ -799,12 +801,16 @@ impl f64 {
     /// assert_eq!(clamped.next_up(), 1.0);
     /// ```
     ///
+    /// This operation corresponds to IEEE-754 `nextDown`.
+    ///
     /// [`NEG_INFINITY`]: Self::NEG_INFINITY
     /// [`INFINITY`]: Self::INFINITY
     /// [`MIN`]: Self::MIN
     /// [`MAX`]: Self::MAX
     #[inline]
-    #[unstable(feature = "float_next_up_down", issue = "91399")]
+    #[doc(alias = "nextDown")]
+    #[stable(feature = "float_next_up_down", since = "CURRENT_RUSTC_VERSION")]
+    #[rustc_const_stable(feature = "float_next_up_down", since = "CURRENT_RUSTC_VERSION")]
     pub const fn next_down(self) -> Self {
         // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
         // denormals to zero. This is in general unsound and unsupported, but here
diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs
index 7912f969bbd..231c8712ebd 100644
--- a/library/std/src/io/mod.rs
+++ b/library/std/src/io/mod.rs
@@ -330,6 +330,7 @@ pub use self::{
 };
 use crate::mem::take;
 use crate::ops::{Deref, DerefMut};
+use crate::sys::anonymous_pipe::{AnonPipe, pipe as pipe_inner};
 use crate::{cmp, fmt, slice, str, sys};
 
 mod buffered;
@@ -3250,3 +3251,251 @@ impl<B: BufRead> Iterator for Lines<B> {
         }
     }
 }
+
+/// Create anonymous pipe that is close-on-exec and blocking.
+///
+/// # Behavior
+///
+/// A pipe is a synchronous, unidirectional data channel between two or more processes, like an
+/// interprocess [`mpsc`](crate::sync::mpsc) provided by the OS. In particular:
+///
+/// * A read on a [`PipeReader`] blocks until the pipe is non-empty.
+/// * A write on a [`PipeWriter`] blocks when the pipe is full.
+/// * When all copies of a [`PipeWriter`] are closed, a read on the corresponding [`PipeReader`]
+///   returns EOF.
+/// * [`PipeReader`] can be shared, but only one process will consume the data in the pipe.
+///
+/// # Capacity
+///
+/// Pipe capacity is platform dependent. To quote the Linux [man page]:
+///
+/// > Different implementations have different limits for the pipe capacity. Applications should
+/// > not rely on a particular capacity: an application should be designed so that a reading process
+/// > consumes data as soon as it is available, so that a writing process does not remain blocked.
+///
+/// # Examples
+///
+/// ```no_run
+/// #![feature(anonymous_pipe)]
+/// # #[cfg(miri)] fn main() {}
+/// # #[cfg(not(miri))]
+/// # fn main() -> std::io::Result<()> {
+/// # use std::process::Command;
+/// # use std::io::{Read, Write};
+/// let (ping_rx, mut ping_tx) = std::io::pipe()?;
+/// let (mut pong_rx, pong_tx) = std::io::pipe()?;
+///
+/// // Spawn a process that echoes its input.
+/// let mut echo_server = Command::new("cat").stdin(ping_rx).stdout(pong_tx).spawn()?;
+///
+/// ping_tx.write_all(b"hello")?;
+/// // Close to unblock echo_server's reader.
+/// drop(ping_tx);
+///
+/// let mut buf = String::new();
+/// // Block until echo_server's writer is closed.
+/// pong_rx.read_to_string(&mut buf)?;
+/// assert_eq!(&buf, "hello");
+///
+/// echo_server.wait()?;
+/// # Ok(())
+/// # }
+/// ```
+/// [pipe]: https://man7.org/linux/man-pages/man2/pipe.2.html
+/// [CreatePipe]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
+/// [man page]: https://man7.org/linux/man-pages/man7/pipe.7.html
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+#[inline]
+pub fn pipe() -> Result<(PipeReader, PipeWriter)> {
+    pipe_inner().map(|(reader, writer)| (PipeReader(reader), PipeWriter(writer)))
+}
+
+/// Read end of the anonymous pipe.
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+#[derive(Debug)]
+pub struct PipeReader(pub(crate) AnonPipe);
+
+/// Write end of the anonymous pipe.
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+#[derive(Debug)]
+pub struct PipeWriter(pub(crate) AnonPipe);
+
+impl PipeReader {
+    /// Create a new [`PipeReader`] instance that shares the same underlying file description.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// #![feature(anonymous_pipe)]
+    /// # #[cfg(miri)] fn main() {}
+    /// # #[cfg(not(miri))]
+    /// # fn main() -> std::io::Result<()> {
+    /// # use std::fs;
+    /// # use std::io::Write;
+    /// # use std::process::Command;
+    /// const NUM_SLOT: u8 = 2;
+    /// const NUM_PROC: u8 = 5;
+    /// const OUTPUT: &str = "work.txt";
+    ///
+    /// let mut jobs = vec![];
+    /// let (reader, mut writer) = std::io::pipe()?;
+    ///
+    /// // Write NUM_SLOT characters the pipe.
+    /// writer.write_all(&[b'|'; NUM_SLOT as usize])?;
+    ///
+    /// // Spawn several processes that read a character from the pipe, do some work, then
+    /// // write back to the pipe. When the pipe is empty, the processes block, so only
+    /// // NUM_SLOT processes can be working at any given time.
+    /// for _ in 0..NUM_PROC {
+    ///     jobs.push(
+    ///         Command::new("bash")
+    ///             .args(["-c",
+    ///                 &format!(
+    ///                      "read -n 1\n\
+    ///                       echo -n 'x' >> '{OUTPUT}'\n\
+    ///                       echo -n '|'",
+    ///                 ),
+    ///             ])
+    ///             .stdin(reader.try_clone()?)
+    ///             .stdout(writer.try_clone()?)
+    ///             .spawn()?,
+    ///     );
+    /// }
+    ///
+    /// // Wait for all jobs to finish.
+    /// for mut job in jobs {
+    ///     job.wait()?;
+    /// }
+    ///
+    /// // Check our work and clean up.
+    /// let xs = fs::read_to_string(OUTPUT)?;
+    /// fs::remove_file(OUTPUT)?;
+    /// assert_eq!(xs, "x".repeat(NUM_PROC.into()));
+    /// # Ok(())
+    /// # }
+    /// ```
+    #[unstable(feature = "anonymous_pipe", issue = "127154")]
+    pub fn try_clone(&self) -> Result<Self> {
+        self.0.try_clone().map(Self)
+    }
+}
+
+impl PipeWriter {
+    /// Create a new [`PipeWriter`] instance that shares the same underlying file description.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// #![feature(anonymous_pipe)]
+    /// # #[cfg(miri)] fn main() {}
+    /// # #[cfg(not(miri))]
+    /// # fn main() -> std::io::Result<()> {
+    /// # use std::process::Command;
+    /// # use std::io::Read;
+    /// let (mut reader, writer) = std::io::pipe()?;
+    ///
+    /// // Spawn a process that writes to stdout and stderr.
+    /// let mut peer = Command::new("bash")
+    ///     .args([
+    ///         "-c",
+    ///         "echo -n foo\n\
+    ///          echo -n bar >&2"
+    ///     ])
+    ///     .stdout(writer.try_clone()?)
+    ///     .stderr(writer)
+    ///     .spawn()?;
+    ///
+    /// // Read and check the result.
+    /// let mut msg = String::new();
+    /// reader.read_to_string(&mut msg)?;
+    /// assert_eq!(&msg, "foobar");
+    ///
+    /// peer.wait()?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    #[unstable(feature = "anonymous_pipe", issue = "127154")]
+    pub fn try_clone(&self) -> Result<Self> {
+        self.0.try_clone().map(Self)
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl Read for &PipeReader {
+    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
+        self.0.read(buf)
+    }
+    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
+        self.0.read_vectored(bufs)
+    }
+    #[inline]
+    fn is_read_vectored(&self) -> bool {
+        self.0.is_read_vectored()
+    }
+    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
+        self.0.read_to_end(buf)
+    }
+    fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
+        self.0.read_buf(buf)
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl Read for PipeReader {
+    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
+        self.0.read(buf)
+    }
+    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
+        self.0.read_vectored(bufs)
+    }
+    #[inline]
+    fn is_read_vectored(&self) -> bool {
+        self.0.is_read_vectored()
+    }
+    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
+        self.0.read_to_end(buf)
+    }
+    fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
+        self.0.read_buf(buf)
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl Write for &PipeWriter {
+    fn write(&mut self, buf: &[u8]) -> Result<usize> {
+        self.0.write(buf)
+    }
+    #[inline]
+    fn flush(&mut self) -> Result<()> {
+        Ok(())
+    }
+
+    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
+        self.0.write_vectored(bufs)
+    }
+
+    #[inline]
+    fn is_write_vectored(&self) -> bool {
+        self.0.is_write_vectored()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl Write for PipeWriter {
+    fn write(&mut self, buf: &[u8]) -> Result<usize> {
+        self.0.write(buf)
+    }
+    #[inline]
+    fn flush(&mut self) -> Result<()> {
+        Ok(())
+    }
+
+    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
+        self.0.write_vectored(bufs)
+    }
+
+    #[inline]
+    fn is_write_vectored(&self) -> bool {
+        self.0.is_write_vectored()
+    }
+}
diff --git a/library/std/src/io/tests.rs b/library/std/src/io/tests.rs
index 47cbb9614af..85098b3bb18 100644
--- a/library/std/src/io/tests.rs
+++ b/library/std/src/io/tests.rs
@@ -823,3 +823,20 @@ fn try_oom_error() {
     let io_err = io::Error::from(reserve_err);
     assert_eq!(io::ErrorKind::OutOfMemory, io_err.kind());
 }
+
+#[test]
+#[cfg(all(windows, unix, not(miri)))]
+fn pipe_creation_clone_and_rw() {
+    let (rx, tx) = std::io::pipe().unwrap();
+
+    tx.try_clone().unwrap().write_all(b"12345").unwrap();
+    drop(tx);
+
+    let mut rx2 = rx.try_clone().unwrap();
+    drop(rx);
+
+    let mut s = String::new();
+    rx2.read_to_string(&mut s).unwrap();
+    drop(rx2);
+    assert_eq!(s, "12345");
+}
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 5c12236617c..39f234e4ba6 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -333,7 +333,6 @@
 #![feature(extend_one)]
 #![feature(float_gamma)]
 #![feature(float_minimum_maximum)]
-#![feature(float_next_up_down)]
 #![feature(fmt_internals)]
 #![feature(hasher_prefixfree_extras)]
 #![feature(hashmap_internals)]
@@ -596,8 +595,6 @@ pub mod panic;
 #[unstable(feature = "pattern_type_macro", issue = "123646")]
 pub mod pat;
 pub mod path;
-#[unstable(feature = "anonymous_pipe", issue = "127154")]
-pub mod pipe;
 pub mod process;
 #[unstable(feature = "random", issue = "130703")]
 pub mod random;
diff --git a/library/std/src/pipe.rs b/library/std/src/pipe.rs
deleted file mode 100644
index 913c22588a7..00000000000
--- a/library/std/src/pipe.rs
+++ /dev/null
@@ -1,258 +0,0 @@
-//!  A cross-platform anonymous pipe.
-//!
-//! This module provides support for anonymous OS pipes, like [pipe] on Linux or [CreatePipe] on
-//! Windows.
-//!
-//! # Behavior
-//!
-//! A pipe is a synchronous, unidirectional data channel between two or more processes, like an
-//! interprocess [`mpsc`](crate::sync::mpsc) provided by the OS. In particular:
-//!
-//! * A read on a [`PipeReader`] blocks until the pipe is non-empty.
-//! * A write on a [`PipeWriter`] blocks when the pipe is full.
-//! * When all copies of a [`PipeWriter`] are closed, a read on the corresponding [`PipeReader`]
-//!   returns EOF.
-//! * [`PipeReader`] can be shared, but only one process will consume the data in the pipe.
-//!
-//! # Capacity
-//!
-//! Pipe capacity is platform dependent. To quote the Linux [man page]:
-//!
-//! > Different implementations have different limits for the pipe capacity. Applications should
-//! > not rely on a particular capacity: an application should be designed so that a reading process
-//! > consumes data as soon as it is available, so that a writing process does not remain blocked.
-//!
-//! # Examples
-//!
-//! ```no_run
-//! #![feature(anonymous_pipe)]
-//! # #[cfg(miri)] fn main() {}
-//! # #[cfg(not(miri))]
-//! # fn main() -> std::io::Result<()> {
-//! # use std::process::Command;
-//! # use std::io::{Read, Write};
-//! let (ping_rx, mut ping_tx) = std::pipe::pipe()?;
-//! let (mut pong_rx, pong_tx) = std::pipe::pipe()?;
-//!
-//! // Spawn a process that echoes its input.
-//! let mut echo_server = Command::new("cat").stdin(ping_rx).stdout(pong_tx).spawn()?;
-//!
-//! ping_tx.write_all(b"hello")?;
-//! // Close to unblock echo_server's reader.
-//! drop(ping_tx);
-//!
-//! let mut buf = String::new();
-//! // Block until echo_server's writer is closed.
-//! pong_rx.read_to_string(&mut buf)?;
-//! assert_eq!(&buf, "hello");
-//!
-//! echo_server.wait()?;
-//! # Ok(())
-//! # }
-//! ```
-//! [pipe]: https://man7.org/linux/man-pages/man2/pipe.2.html
-//! [CreatePipe]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
-//! [man page]: https://man7.org/linux/man-pages/man7/pipe.7.html
-use crate::io;
-use crate::sys::anonymous_pipe::{AnonPipe, pipe as pipe_inner};
-
-/// Create anonymous pipe that is close-on-exec and blocking.
-///
-/// # Examples
-///
-/// See the [module-level](crate::pipe) documentation for examples.
-#[unstable(feature = "anonymous_pipe", issue = "127154")]
-#[inline]
-pub fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
-    pipe_inner().map(|(reader, writer)| (PipeReader(reader), PipeWriter(writer)))
-}
-
-/// Read end of the anonymous pipe.
-#[unstable(feature = "anonymous_pipe", issue = "127154")]
-#[derive(Debug)]
-pub struct PipeReader(pub(crate) AnonPipe);
-
-/// Write end of the anonymous pipe.
-#[unstable(feature = "anonymous_pipe", issue = "127154")]
-#[derive(Debug)]
-pub struct PipeWriter(pub(crate) AnonPipe);
-
-impl PipeReader {
-    /// Create a new [`PipeReader`] instance that shares the same underlying file description.
-    ///
-    /// # Examples
-    ///
-    /// ```no_run
-    /// #![feature(anonymous_pipe)]
-    /// # #[cfg(miri)] fn main() {}
-    /// # #[cfg(not(miri))]
-    /// # fn main() -> std::io::Result<()> {
-    /// # use std::fs;
-    /// # use std::io::Write;
-    /// # use std::process::Command;
-    /// const NUM_SLOT: u8 = 2;
-    /// const NUM_PROC: u8 = 5;
-    /// const OUTPUT: &str = "work.txt";
-    ///
-    /// let mut jobs = vec![];
-    /// let (reader, mut writer) = std::pipe::pipe()?;
-    ///
-    /// // Write NUM_SLOT characters the pipe.
-    /// writer.write_all(&[b'|'; NUM_SLOT as usize])?;
-    ///
-    /// // Spawn several processes that read a character from the pipe, do some work, then
-    /// // write back to the pipe. When the pipe is empty, the processes block, so only
-    /// // NUM_SLOT processes can be working at any given time.
-    /// for _ in 0..NUM_PROC {
-    ///     jobs.push(
-    ///         Command::new("bash")
-    ///             .args(["-c",
-    ///                 &format!(
-    ///                      "read -n 1\n\
-    ///                       echo -n 'x' >> '{OUTPUT}'\n\
-    ///                       echo -n '|'",
-    ///                 ),
-    ///             ])
-    ///             .stdin(reader.try_clone()?)
-    ///             .stdout(writer.try_clone()?)
-    ///             .spawn()?,
-    ///     );
-    /// }
-    ///
-    /// // Wait for all jobs to finish.
-    /// for mut job in jobs {
-    ///     job.wait()?;
-    /// }
-    ///
-    /// // Check our work and clean up.
-    /// let xs = fs::read_to_string(OUTPUT)?;
-    /// fs::remove_file(OUTPUT)?;
-    /// assert_eq!(xs, "x".repeat(NUM_PROC.into()));
-    /// # Ok(())
-    /// # }
-    /// ```
-    #[unstable(feature = "anonymous_pipe", issue = "127154")]
-    pub fn try_clone(&self) -> io::Result<Self> {
-        self.0.try_clone().map(Self)
-    }
-}
-
-impl PipeWriter {
-    /// Create a new [`PipeWriter`] instance that shares the same underlying file description.
-    ///
-    /// # Examples
-    ///
-    /// ```no_run
-    /// #![feature(anonymous_pipe)]
-    /// # #[cfg(miri)] fn main() {}
-    /// # #[cfg(not(miri))]
-    /// # fn main() -> std::io::Result<()> {
-    /// # use std::process::Command;
-    /// # use std::io::Read;
-    /// let (mut reader, writer) = std::pipe::pipe()?;
-    ///
-    /// // Spawn a process that writes to stdout and stderr.
-    /// let mut peer = Command::new("bash")
-    ///     .args([
-    ///         "-c",
-    ///         "echo -n foo\n\
-    ///          echo -n bar >&2"
-    ///     ])
-    ///     .stdout(writer.try_clone()?)
-    ///     .stderr(writer)
-    ///     .spawn()?;
-    ///
-    /// // Read and check the result.
-    /// let mut msg = String::new();
-    /// reader.read_to_string(&mut msg)?;
-    /// assert_eq!(&msg, "foobar");
-    ///
-    /// peer.wait()?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    #[unstable(feature = "anonymous_pipe", issue = "127154")]
-    pub fn try_clone(&self) -> io::Result<Self> {
-        self.0.try_clone().map(Self)
-    }
-}
-
-#[unstable(feature = "anonymous_pipe", issue = "127154")]
-impl io::Read for &PipeReader {
-    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        self.0.read(buf)
-    }
-    fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
-        self.0.read_vectored(bufs)
-    }
-    #[inline]
-    fn is_read_vectored(&self) -> bool {
-        self.0.is_read_vectored()
-    }
-    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
-        self.0.read_to_end(buf)
-    }
-    fn read_buf(&mut self, buf: io::BorrowedCursor<'_>) -> io::Result<()> {
-        self.0.read_buf(buf)
-    }
-}
-
-#[unstable(feature = "anonymous_pipe", issue = "127154")]
-impl io::Read for PipeReader {
-    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        self.0.read(buf)
-    }
-    fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
-        self.0.read_vectored(bufs)
-    }
-    #[inline]
-    fn is_read_vectored(&self) -> bool {
-        self.0.is_read_vectored()
-    }
-    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
-        self.0.read_to_end(buf)
-    }
-    fn read_buf(&mut self, buf: io::BorrowedCursor<'_>) -> io::Result<()> {
-        self.0.read_buf(buf)
-    }
-}
-
-#[unstable(feature = "anonymous_pipe", issue = "127154")]
-impl io::Write for &PipeWriter {
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        self.0.write(buf)
-    }
-    #[inline]
-    fn flush(&mut self) -> io::Result<()> {
-        Ok(())
-    }
-
-    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
-        self.0.write_vectored(bufs)
-    }
-
-    #[inline]
-    fn is_write_vectored(&self) -> bool {
-        self.0.is_write_vectored()
-    }
-}
-
-#[unstable(feature = "anonymous_pipe", issue = "127154")]
-impl io::Write for PipeWriter {
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        self.0.write(buf)
-    }
-    #[inline]
-    fn flush(&mut self) -> io::Result<()> {
-        Ok(())
-    }
-
-    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
-        self.0.write_vectored(bufs)
-    }
-
-    #[inline]
-    fn is_write_vectored(&self) -> bool {
-        self.0.is_write_vectored()
-    }
-}
diff --git a/library/std/src/pipe/tests.rs b/library/std/src/pipe/tests.rs
deleted file mode 100644
index 9c38e106787..00000000000
--- a/library/std/src/pipe/tests.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use crate::io::{Read, Write};
-use crate::pipe::pipe;
-
-#[test]
-#[cfg(all(windows, unix, not(miri)))]
-fn pipe_creation_clone_and_rw() {
-    let (rx, tx) = pipe().unwrap();
-
-    tx.try_clone().unwrap().write_all(b"12345").unwrap();
-    drop(tx);
-
-    let mut rx2 = rx.try_clone().unwrap();
-    drop(rx);
-
-    let mut s = String::new();
-    rx2.read_to_string(&mut s).unwrap();
-    drop(rx2);
-    assert_eq!(s, "12345");
-}
diff --git a/library/std/src/sys/anonymous_pipe/unix.rs b/library/std/src/sys/anonymous_pipe/unix.rs
index 9168024730e..9e398765634 100644
--- a/library/std/src/sys/anonymous_pipe/unix.rs
+++ b/library/std/src/sys/anonymous_pipe/unix.rs
@@ -1,6 +1,5 @@
-use crate::io;
+use crate::io::{self, PipeReader, PipeWriter};
 use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
-use crate::pipe::{PipeReader, PipeWriter};
 use crate::process::Stdio;
 use crate::sys::fd::FileDesc;
 use crate::sys::pipe::anon_pipe;
diff --git a/library/std/src/sys/anonymous_pipe/unsupported.rs b/library/std/src/sys/anonymous_pipe/unsupported.rs
index dd51e70315e..4e79ac9c21a 100644
--- a/library/std/src/sys/anonymous_pipe/unsupported.rs
+++ b/library/std/src/sys/anonymous_pipe/unsupported.rs
@@ -1,5 +1,4 @@
-use crate::io;
-use crate::pipe::{PipeReader, PipeWriter};
+use crate::io::{self, PipeReader, PipeWriter};
 use crate::process::Stdio;
 pub use crate::sys::pipe::AnonPipe;
 
diff --git a/library/std/src/sys/anonymous_pipe/windows.rs b/library/std/src/sys/anonymous_pipe/windows.rs
index a48198f8a81..eb7fa8ec1c9 100644
--- a/library/std/src/sys/anonymous_pipe/windows.rs
+++ b/library/std/src/sys/anonymous_pipe/windows.rs
@@ -1,12 +1,12 @@
+use crate::io::{self, PipeReader, PipeWriter};
 use crate::os::windows::io::{
     AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle,
 };
-use crate::pipe::{PipeReader, PipeWriter};
 use crate::process::Stdio;
+use crate::ptr;
 use crate::sys::c;
 use crate::sys::handle::Handle;
 use crate::sys_common::{FromInner, IntoInner};
-use crate::{io, ptr};
 
 pub type AnonPipe = Handle;
 
diff --git a/library/std/src/sys/pal/unix/kernel_copy.rs b/library/std/src/sys/pal/unix/kernel_copy.rs
index 36823a503b1..bbf29f32523 100644
--- a/library/std/src/sys/pal/unix/kernel_copy.rs
+++ b/library/std/src/sys/pal/unix/kernel_copy.rs
@@ -52,15 +52,14 @@ use crate::cmp::min;
 use crate::fs::{File, Metadata};
 use crate::io::copy::generic_copy;
 use crate::io::{
-    BufRead, BufReader, BufWriter, Error, Read, Result, StderrLock, StdinLock, StdoutLock, Take,
-    Write,
+    BufRead, BufReader, BufWriter, Error, PipeReader, PipeWriter, Read, Result, StderrLock,
+    StdinLock, StdoutLock, Take, Write,
 };
 use crate::mem::ManuallyDrop;
 use crate::net::TcpStream;
 use crate::os::unix::fs::FileTypeExt;
 use crate::os::unix::io::{AsRawFd, FromRawFd, RawFd};
 use crate::os::unix::net::UnixStream;
-use crate::pipe::{PipeReader, PipeWriter};
 use crate::process::{ChildStderr, ChildStdin, ChildStdout};
 use crate::ptr;
 use crate::sync::atomic::{AtomicBool, AtomicU8, Ordering};
diff --git a/library/std/tests/pipe_subprocess.rs b/library/std/tests/pipe_subprocess.rs
index 1535742a83a..df946cdcf2b 100644
--- a/library/std/tests/pipe_subprocess.rs
+++ b/library/std/tests/pipe_subprocess.rs
@@ -3,8 +3,7 @@
 fn main() {
     #[cfg(all(not(miri), any(unix, windows)))]
     {
-        use std::io::Read;
-        use std::pipe::pipe;
+        use std::io::{Read, pipe};
         use std::{env, process};
 
         if env::var("I_AM_THE_CHILD").is_ok() {
diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs
index c3375b69961..fe8c89f7a53 100644
--- a/src/bootstrap/src/core/build_steps/clippy.rs
+++ b/src/bootstrap/src/core/build_steps/clippy.rs
@@ -334,6 +334,7 @@ lint_any!(
     CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri";
     Clippy, "src/tools/clippy", "clippy";
     CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata";
+    CodegenGcc, "compiler/rustc_codegen_gcc", "rustc-codegen-gcc";
     Compiletest, "src/tools/compiletest", "compiletest";
     CoverageDump, "src/tools/coverage-dump", "coverage-dump";
     Jsondocck, "src/tools/jsondocck", "jsondocck";
@@ -400,6 +401,12 @@ impl Step for CI {
             ],
             forbid: vec![],
         };
+        builder.ensure(Std {
+            target: self.target,
+            config: self.config.merge(&library_clippy_cfg),
+            crates: vec![],
+        });
+
         let compiler_clippy_cfg = LintConfig {
             allow: vec!["clippy::all".into()],
             warn: vec![],
@@ -419,16 +426,21 @@ impl Step for CI {
             ],
             forbid: vec![],
         };
-
-        builder.ensure(Std {
-            target: self.target,
-            config: self.config.merge(&library_clippy_cfg),
-            crates: vec![],
-        });
         builder.ensure(Rustc {
             target: self.target,
             config: self.config.merge(&compiler_clippy_cfg),
             crates: vec![],
         });
+
+        let rustc_codegen_gcc = LintConfig {
+            allow: vec![],
+            warn: vec![],
+            deny: vec!["warnings".into()],
+            forbid: vec![],
+        };
+        builder.ensure(CodegenGcc {
+            target: self.target,
+            config: self.config.merge(&rustc_codegen_gcc),
+        });
     }
 }
diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs
index afa409aa835..470e400243f 100644
--- a/src/bootstrap/src/core/build_steps/dist.rs
+++ b/src/bootstrap/src/core/build_steps/dist.rs
@@ -994,20 +994,24 @@ impl Step for PlainSourceTarball {
 
         // This is the set of root paths which will become part of the source package
         let src_files = [
+            // tidy-alphabetical-start
+            ".gitmodules",
+            "Cargo.lock",
+            "Cargo.toml",
+            "config.example.toml",
+            "configure",
+            "CONTRIBUTING.md",
             "COPYRIGHT",
             "LICENSE-APACHE",
+            "license-metadata.json",
             "LICENSE-MIT",
-            "CONTRIBUTING.md",
             "README.md",
             "RELEASES.md",
             "REUSE.toml",
-            "license-metadata.json",
-            "configure",
+            "x",
+            "x.ps1",
             "x.py",
-            "config.example.toml",
-            "Cargo.toml",
-            "Cargo.lock",
-            ".gitmodules",
+            // tidy-alphabetical-end
         ];
         let src_dirs = ["src", "compiler", "library", "tests", "LICENSES"];
 
diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs
index 17de1762427..b293ac4f351 100644
--- a/src/bootstrap/src/core/builder/mod.rs
+++ b/src/bootstrap/src/core/builder/mod.rs
@@ -900,6 +900,7 @@ impl<'a> Builder<'a> {
                 clippy::BuildManifest,
                 clippy::CargoMiri,
                 clippy::Clippy,
+                clippy::CodegenGcc,
                 clippy::CollectLicenseMetadata,
                 clippy::Compiletest,
                 clippy::CoverageDump,
diff --git a/tests/run-make/broken-pipe-no-ice/rmake.rs b/tests/run-make/broken-pipe-no-ice/rmake.rs
index 378c3289cb7..54d13b62f4a 100644
--- a/tests/run-make/broken-pipe-no-ice/rmake.rs
+++ b/tests/run-make/broken-pipe-no-ice/rmake.rs
@@ -25,7 +25,7 @@ enum Binary {
 }
 
 fn check_broken_pipe_handled_gracefully(bin: Binary, mut cmd: Command) {
-    let (reader, writer) = std::pipe::pipe().unwrap();
+    let (reader, writer) = std::io::pipe().unwrap();
     drop(reader); // close read-end
     cmd.stdout(writer).stderr(Stdio::piped());
 
diff --git a/tests/ui/consts/const-ptr-is-null.rs b/tests/ui/consts/const-ptr-is-null.rs
index 92cf87a9782..0abd9afa422 100644
--- a/tests/ui/consts/const-ptr-is-null.rs
+++ b/tests/ui/consts/const-ptr-is-null.rs
@@ -12,7 +12,13 @@ const MAYBE_NULL: () = {
     let ptr = &x as *const i32;
     // This one is still unambiguous...
     assert!(!ptr.is_null());
-    // but once we shift outside the allocation, we might become null.
+    // and in fact, any offset not visible by 4 (the alignment) cannot be null,
+    // even if it goes out-of-bounds...
+    assert!(!ptr.wrapping_byte_add(13).is_null());
+    assert!(!ptr.wrapping_byte_add(18).is_null());
+    assert!(!ptr.wrapping_byte_sub(1).is_null());
+    // ... but once we shift outside the allocation, with an offset divisible by 4,
+    // we might become null.
     assert!(!ptr.wrapping_sub(512).is_null()); //~inside `MAYBE_NULL`
 };
 
diff --git a/tests/ui/consts/const-ptr-is-null.stderr b/tests/ui/consts/const-ptr-is-null.stderr
index f71b3752772..5ef79790d93 100644
--- a/tests/ui/consts/const-ptr-is-null.stderr
+++ b/tests/ui/consts/const-ptr-is-null.stderr
@@ -8,7 +8,7 @@ note: inside `std::ptr::const_ptr::<impl *const T>::is_null::compiletime`
 note: inside `std::ptr::const_ptr::<impl *const i32>::is_null`
   --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
 note: inside `MAYBE_NULL`
-  --> $DIR/const-ptr-is-null.rs:16:14
+  --> $DIR/const-ptr-is-null.rs:22:14
    |
 LL |     assert!(!ptr.wrapping_sub(512).is_null());
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^