about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-09-26 07:45:10 +0000
committerbors <bors@rust-lang.org>2025-09-26 07:45:10 +0000
commit5b9007bfc358817cf066ee27c6b440440727d3a7 (patch)
treecb225ce4e1374e812aea32738daed429e2a0814c
parent40ace17fc3891155bad26a50d60a9ab07b83bf8e (diff)
parent87b8eca51683252e9c27cd9cb2f456c3ee134cdb (diff)
downloadrust-5b9007bfc358817cf066ee27c6b440440727d3a7.tar.gz
rust-5b9007bfc358817cf066ee27c6b440440727d3a7.zip
Auto merge of #147054 - matthiaskrgr:rollup-660g92w, r=matthiaskrgr
Rollup of 7 pull requests

Successful merges:

 - rust-lang/rust#146283 (Resolve: (Ref)Cell wrappers to deny mutation during spec resolution.)
 - rust-lang/rust#146453 (Add general arm-linux.md platform doc.)
 - rust-lang/rust#146991 (const_caller_location to use real Span instead of `DUMMY_SP`)
 - rust-lang/rust#146994 (Add `clippy::unconditional_recursion` to `./x clippy ci`)
 - rust-lang/rust#147038 (Rename verbosity functions in bootstrap)
 - rust-lang/rust#147047 (rustdoc: put the toolbar on the all item index)
 - rust-lang/rust#147049 (std: fix warning in VEXos stdio module)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--compiler/rustc_const_eval/src/util/caller_location.rs2
-rw-r--r--compiler/rustc_resolve/src/build_reduced_graph.rs19
-rw-r--r--compiler/rustc_resolve/src/imports.rs26
-rw-r--r--compiler/rustc_resolve/src/lib.rs128
-rw-r--r--compiler/rustc_resolve/src/macros.rs2
-rw-r--r--library/std/src/sys/stdio/vexos.rs2
-rw-r--r--src/bootstrap/src/core/build_steps/clippy.rs2
-rw-r--r--src/bootstrap/src/core/build_steps/compile.rs14
-rw-r--r--src/bootstrap/src/core/build_steps/dist.rs2
-rw-r--r--src/bootstrap/src/core/build_steps/gcc.rs2
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs6
-rw-r--r--src/bootstrap/src/core/builder/cargo.rs2
-rw-r--r--src/bootstrap/src/core/builder/mod.rs4
-rw-r--r--src/bootstrap/src/core/config/config.rs6
-rw-r--r--src/bootstrap/src/core/download.rs18
-rw-r--r--src/bootstrap/src/lib.rs28
-rw-r--r--src/bootstrap/src/utils/build_stamp.rs2
-rw-r--r--src/bootstrap/src/utils/cc_detect.rs10
-rw-r--r--src/bootstrap/src/utils/exec.rs6
-rw-r--r--src/bootstrap/src/utils/render_tests.rs2
-rw-r--r--src/bootstrap/src/utils/tarball.rs2
-rw-r--r--src/doc/rustc/src/SUMMARY.md9
-rw-r--r--src/doc/rustc/src/platform-support/arm-linux.md217
-rw-r--r--src/doc/rustc/src/platform-support/armeb-unknown-linux-gnueabi.md3
-rw-r--r--src/doc/rustc/src/platform-support/armv5te-unknown-linux-gnueabi.md3
-rw-r--r--src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabi.md3
-rw-r--r--src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabihf.md3
-rw-r--r--src/librustdoc/html/render/mod.rs7
-rw-r--r--src/librustdoc/html/render/write_shared.rs9
-rw-r--r--tests/rustdoc-gui/search-title.goml12
-rw-r--r--tests/rustdoc-gui/src/test_docs/lib.rs1
31 files changed, 445 insertions, 107 deletions
diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs
index 5249b32eca4..4e7c8310007 100644
--- a/compiler/rustc_const_eval/src/util/caller_location.rs
+++ b/compiler/rustc_const_eval/src/util/caller_location.rs
@@ -61,7 +61,7 @@ pub(crate) fn const_caller_location_provider(
     trace!("const_caller_location: {}:{}:{}", file, line, col);
     let mut ecx = mk_eval_cx_to_read_const_val(
         tcx,
-        rustc_span::DUMMY_SP, // FIXME: use a proper span here?
+        rustc_span::DUMMY_SP, // This interpreter cannot fail, so the span is irrelevant.
         ty::TypingEnv::fully_monomorphized(),
         CanAccessMutGlobal::No,
     );
diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs
index fa3c06059b3..c10b6ca7e71 100644
--- a/compiler/rustc_resolve/src/build_reduced_graph.rs
+++ b/compiler/rustc_resolve/src/build_reduced_graph.rs
@@ -5,7 +5,6 @@
 //! unexpanded macros in the fragment are visited and registered.
 //! Imports are also considered items and placed into modules here, but not resolved yet.
 
-use std::cell::Cell;
 use std::sync::Arc;
 
 use rustc_ast::visit::{self, AssocCtxt, Visitor, WalkItemKind};
@@ -35,6 +34,7 @@ use crate::Namespace::{MacroNS, TypeNS, ValueNS};
 use crate::def_collector::collect_definitions;
 use crate::imports::{ImportData, ImportKind};
 use crate::macros::{MacroRulesBinding, MacroRulesScope, MacroRulesScopeRef};
+use crate::ref_mut::CmCell;
 use crate::{
     BindingKey, ExternPreludeEntry, Finalize, MacroData, Module, ModuleKind, ModuleOrUniformRoot,
     NameBinding, ParentScope, PathResult, ResolutionError, Resolver, Segment, Used,
@@ -87,7 +87,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         // because they can be fetched by glob imports from those modules, and bring traits
         // into scope both directly and through glob imports.
         let key = BindingKey::new_disambiguated(ident, ns, || {
-            parent.underscore_disambiguator.update(|d| d + 1);
+            // FIXME(batched): Will be fixed in batched resolution.
+            parent.underscore_disambiguator.update_unchecked(|d| d + 1);
             parent.underscore_disambiguator.get()
         });
         if self
@@ -477,7 +478,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
             kind,
             parent_scope: self.parent_scope,
             module_path,
-            imported_module: Cell::new(None),
+            imported_module: CmCell::new(None),
             span,
             use_span: item.span,
             use_span_with_attributes: item.span_with_attributes(),
@@ -505,7 +506,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
                     });
                 }
             }
-            ImportKind::Glob { .. } => current_module.globs.borrow_mut().push(import),
+            ImportKind::Glob { .. } => current_module.globs.borrow_mut(self.r).push(import),
             _ => unreachable!(),
         }
     }
@@ -668,7 +669,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
             }
             ast::UseTreeKind::Glob => {
                 if !ast::attr::contains_name(&item.attrs, sym::prelude_import) {
-                    let kind = ImportKind::Glob { max_vis: Cell::new(None), id };
+                    let kind = ImportKind::Glob { max_vis: CmCell::new(None), id };
                     self.add_import(prefix, kind, use_tree.span, item, root_span, item.id, vis);
                 } else {
                     // Resolve the prelude import early.
@@ -971,7 +972,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
             kind: ImportKind::ExternCrate { source: orig_name, target: ident, id: item.id },
             root_id: item.id,
             parent_scope: self.parent_scope,
-            imported_module: Cell::new(module),
+            imported_module: CmCell::new(module),
             has_attributes: !item.attrs.is_empty(),
             use_span_with_attributes: item.span_with_attributes(),
             use_span: item.span,
@@ -1103,7 +1104,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
                 kind: ImportKind::MacroUse { warn_private },
                 root_id: item.id,
                 parent_scope: this.parent_scope,
-                imported_module: Cell::new(Some(ModuleOrUniformRoot::Module(module))),
+                imported_module: CmCell::new(Some(ModuleOrUniformRoot::Module(module))),
                 use_span_with_attributes: item.span_with_attributes(),
                 has_attributes: !item.attrs.is_empty(),
                 use_span: item.span,
@@ -1196,7 +1197,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
     /// directly into its parent scope's module.
     fn visit_invoc_in_module(&mut self, id: NodeId) -> MacroRulesScopeRef<'ra> {
         let invoc_id = self.visit_invoc(id);
-        self.parent_scope.module.unexpanded_invocations.borrow_mut().insert(invoc_id);
+        self.parent_scope.module.unexpanded_invocations.borrow_mut(self.r).insert(invoc_id);
         self.r.arenas.alloc_macro_rules_scope(MacroRulesScope::Invocation(invoc_id))
     }
 
@@ -1274,7 +1275,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
                     kind: ImportKind::MacroExport,
                     root_id: item.id,
                     parent_scope: self.parent_scope,
-                    imported_module: Cell::new(None),
+                    imported_module: CmCell::new(None),
                     has_attributes: false,
                     use_span_with_attributes: span,
                     use_span: span,
diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs
index 33c2c7436d1..ce90a1bcd31 100644
--- a/compiler/rustc_resolve/src/imports.rs
+++ b/compiler/rustc_resolve/src/imports.rs
@@ -1,6 +1,5 @@
 //! A bunch of methods and structures more or less related to resolving imports.
 
-use std::cell::Cell;
 use std::mem;
 
 use rustc_ast::NodeId;
@@ -32,6 +31,7 @@ use crate::errors::{
     CannotBeReexportedPrivateNS, CannotDetermineImportResolution, CannotGlobImportAllCrates,
     ConsiderAddingMacroExport, ConsiderMarkingAsPub, ConsiderMarkingAsPubCrate,
 };
+use crate::ref_mut::CmCell;
 use crate::{
     AmbiguityError, AmbiguityKind, BindingKey, CmResolver, Determinacy, Finalize, ImportSuggestion,
     Module, ModuleOrUniformRoot, NameBinding, NameBindingData, NameBindingKind, ParentScope,
@@ -68,7 +68,7 @@ pub(crate) enum ImportKind<'ra> {
         /// It will directly use `source` when the format is `use prefix::source`.
         target: Ident,
         /// Bindings introduced by the import.
-        bindings: PerNS<Cell<PendingBinding<'ra>>>,
+        bindings: PerNS<CmCell<PendingBinding<'ra>>>,
         /// `true` for `...::{self [as target]}` imports, `false` otherwise.
         type_ns_only: bool,
         /// Did this import result from a nested import? ie. `use foo::{bar, baz};`
@@ -89,7 +89,7 @@ pub(crate) enum ImportKind<'ra> {
     Glob {
         // The visibility of the greatest re-export.
         // n.b. `max_vis` is only used in `finalize_import` to check for re-export errors.
-        max_vis: Cell<Option<Visibility>>,
+        max_vis: CmCell<Option<Visibility>>,
         id: NodeId,
     },
     ExternCrate {
@@ -182,7 +182,7 @@ pub(crate) struct ImportData<'ra> {
     /// |`use ::foo`      | `ModuleOrUniformRoot::ExternPrelude`          | 2018+ editions |
     /// |`use ::foo`      | `ModuleOrUniformRoot::ModuleAndExternPrelude` | a special case in 2015 edition |
     /// |`use foo`        | `ModuleOrUniformRoot::CurrentScope`           | - |
-    pub imported_module: Cell<Option<ModuleOrUniformRoot<'ra>>>,
+    pub imported_module: CmCell<Option<ModuleOrUniformRoot<'ra>>>,
     pub vis: Visibility,
 
     /// Span of the visibility.
@@ -320,7 +320,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             && (vis == import_vis
                 || max_vis.get().is_none_or(|max_vis| vis.is_at_least(max_vis, self.tcx)))
         {
-            max_vis.set(Some(vis.expect_local()))
+            // FIXME(batched): Will be fixed in batched import resolution.
+            max_vis.set_unchecked(Some(vis.expect_local()))
         }
 
         self.arenas.alloc_name_binding(NameBindingData {
@@ -349,7 +350,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         // because they can be fetched by glob imports from those modules, and bring traits
         // into scope both directly and through glob imports.
         let key = BindingKey::new_disambiguated(ident, ns, || {
-            module.underscore_disambiguator.update(|d| d + 1);
+            // FIXME(batched): Will be fixed in batched resolution.
+            module.underscore_disambiguator.update_unchecked(|d| d + 1);
             module.underscore_disambiguator.get()
         });
         self.update_local_resolution(module, key, warn_ambiguity, |this, resolution| {
@@ -482,7 +484,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             }
         };
 
-        let Ok(glob_importers) = module.glob_importers.try_borrow_mut() else {
+        let Ok(glob_importers) = module.glob_importers.try_borrow_mut_unchecked() else {
             return t;
         };
 
@@ -862,7 +864,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             }
         };
 
-        import.imported_module.set(Some(module));
+        // FIXME(batched): Will be fixed in batched import resolution.
+        import.imported_module.set_unchecked(Some(module));
         let (source, target, bindings, type_ns_only) = match import.kind {
             ImportKind::Single { source, target, ref bindings, type_ns_only, .. } => {
                 (source, target, bindings, type_ns_only)
@@ -937,7 +940,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                         PendingBinding::Pending
                     }
                 };
-                bindings[ns].set(binding);
+                // FIXME(batched): Will be fixed in batched import resolution.
+                bindings[ns].set_unchecked(binding);
             }
         });
 
@@ -1508,7 +1512,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         }
 
         // Add to module's glob_importers
-        module.glob_importers.borrow_mut().push(import);
+        module.glob_importers.borrow_mut_unchecked().push(import);
 
         // Ensure that `resolutions` isn't borrowed during `try_define`,
         // since it might get updated via a glob cycle.
@@ -1550,7 +1554,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
     // reporting conflicts, and reporting unresolved imports.
     fn finalize_resolutions_in(&mut self, module: Module<'ra>) {
         // Since import resolution is finished, globs will not define any more names.
-        *module.globs.borrow_mut() = Vec::new();
+        *module.globs.borrow_mut(self) = Vec::new();
 
         let Some(def_id) = module.opt_def_id() else { return };
 
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 8b185ce7ef2..8959068b2a6 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -19,6 +19,7 @@
 #![feature(default_field_values)]
 #![feature(if_let_guard)]
 #![feature(iter_intersperse)]
+#![feature(ptr_as_ref_unchecked)]
 #![feature(rustc_attrs)]
 #![feature(rustdoc_internals)]
 #![recursion_limit = "256"]
@@ -26,7 +27,7 @@
 
 use std::cell::{Cell, Ref, RefCell};
 use std::collections::BTreeSet;
-use std::fmt;
+use std::fmt::{self};
 use std::sync::Arc;
 
 use diagnostics::{ImportSuggestion, LabelSuggestion, Suggestion};
@@ -95,6 +96,8 @@ pub mod rustdoc;
 
 pub use macros::registered_tools_ast;
 
+use crate::ref_mut::{CmCell, CmRefCell};
+
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
 
 #[derive(Debug)]
@@ -592,22 +595,22 @@ struct ModuleData<'ra> {
     /// Resolutions in modules from other crates are not populated until accessed.
     lazy_resolutions: Resolutions<'ra>,
     /// True if this is a module from other crate that needs to be populated on access.
-    populate_on_access: Cell<bool>,
+    populate_on_access: Cell<bool>, // FIXME(parallel): Use an atomic in parallel import resolution
     /// Used to disambiguate underscore items (`const _: T = ...`) in the module.
-    underscore_disambiguator: Cell<u32>,
+    underscore_disambiguator: CmCell<u32>,
 
     /// Macro invocations that can expand into items in this module.
-    unexpanded_invocations: RefCell<FxHashSet<LocalExpnId>>,
+    unexpanded_invocations: CmRefCell<FxHashSet<LocalExpnId>>,
 
     /// Whether `#[no_implicit_prelude]` is active.
     no_implicit_prelude: bool,
 
-    glob_importers: RefCell<Vec<Import<'ra>>>,
-    globs: RefCell<Vec<Import<'ra>>>,
+    glob_importers: CmRefCell<Vec<Import<'ra>>>,
+    globs: CmRefCell<Vec<Import<'ra>>>,
 
     /// Used to memoize the traits in this module for faster searches through all traits in scope.
     traits:
-        RefCell<Option<Box<[(Macros20NormalizedIdent, NameBinding<'ra>, Option<Module<'ra>>)]>>>,
+        CmRefCell<Option<Box<[(Macros20NormalizedIdent, NameBinding<'ra>, Option<Module<'ra>>)]>>>,
 
     /// Span of the module itself. Used for error reporting.
     span: Span,
@@ -656,12 +659,12 @@ impl<'ra> ModuleData<'ra> {
             kind,
             lazy_resolutions: Default::default(),
             populate_on_access: Cell::new(is_foreign),
-            underscore_disambiguator: Cell::new(0),
+            underscore_disambiguator: CmCell::new(0),
             unexpanded_invocations: Default::default(),
             no_implicit_prelude,
-            glob_importers: RefCell::new(Vec::new()),
-            globs: RefCell::new(Vec::new()),
-            traits: RefCell::new(None),
+            glob_importers: CmRefCell::new(Vec::new()),
+            globs: CmRefCell::new(Vec::new()),
+            traits: CmRefCell::new(None),
             span,
             expansion,
             self_binding,
@@ -696,7 +699,7 @@ impl<'ra> Module<'ra> {
 
     /// This modifies `self` in place. The traits will be stored in `self.traits`.
     fn ensure_traits<'tcx>(self, resolver: &impl AsRef<Resolver<'ra, 'tcx>>) {
-        let mut traits = self.traits.borrow_mut();
+        let mut traits = self.traits.borrow_mut(resolver.as_ref());
         if traits.is_none() {
             let mut collected_traits = Vec::new();
             self.for_each_child(resolver, |r, name, ns, binding| {
@@ -1974,6 +1977,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
 
     fn resolutions(&self, module: Module<'ra>) -> &'ra Resolutions<'ra> {
         if module.populate_on_access.get() {
+            // FIXME(batched): Will be fixed in batched import resolution.
             module.populate_on_access.set(false);
             self.build_reduced_graph_external(module);
         }
@@ -2504,9 +2508,20 @@ pub fn provide(providers: &mut Providers) {
     providers.registered_tools = macros::registered_tools;
 }
 
+/// A wrapper around `&mut Resolver` that may be mutable or immutable, depending on a conditions.
+///
+/// `Cm` stands for "conditionally mutable".
+///
+/// Prefer constructing it through [`Resolver::cm`] to ensure correctness.
+type CmResolver<'r, 'ra, 'tcx> = ref_mut::RefOrMut<'r, Resolver<'ra, 'tcx>>;
+
 mod ref_mut {
+    use std::cell::{BorrowMutError, Cell, Ref, RefCell, RefMut};
+    use std::fmt;
     use std::ops::Deref;
 
+    use crate::Resolver;
+
     /// A wrapper around a mutable reference that conditionally allows mutable access.
     pub(crate) struct RefOrMut<'a, T> {
         p: &'a mut T,
@@ -2555,11 +2570,86 @@ mod ref_mut {
             self.p
         }
     }
-}
 
-/// A wrapper around `&mut Resolver` that may be mutable or immutable, depending on a conditions.
-///
-/// `Cm` stands for "conditionally mutable".
-///
-/// Prefer constructing it through [`Resolver::cm`] to ensure correctness.
-type CmResolver<'r, 'ra, 'tcx> = ref_mut::RefOrMut<'r, Resolver<'ra, 'tcx>>;
+    /// A wrapper around a [`Cell`] that only allows mutation based on a condition in the resolver.
+    #[derive(Default)]
+    pub(crate) struct CmCell<T>(Cell<T>);
+
+    impl<T: Copy + fmt::Debug> fmt::Debug for CmCell<T> {
+        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+            f.debug_tuple("CmCell").field(&self.get()).finish()
+        }
+    }
+
+    impl<T: Copy> Clone for CmCell<T> {
+        #[inline]
+        fn clone(&self) -> CmCell<T> {
+            CmCell::new(self.get())
+        }
+    }
+
+    impl<T: Copy> CmCell<T> {
+        pub(crate) const fn get(&self) -> T {
+            self.0.get()
+        }
+
+        pub(crate) fn update_unchecked(&self, f: impl FnOnce(T) -> T)
+        where
+            T: Copy,
+        {
+            let old = self.get();
+            self.set_unchecked(f(old));
+        }
+    }
+
+    impl<T> CmCell<T> {
+        pub(crate) const fn new(value: T) -> CmCell<T> {
+            CmCell(Cell::new(value))
+        }
+
+        pub(crate) fn set_unchecked(&self, val: T) {
+            self.0.set(val);
+        }
+
+        pub(crate) fn into_inner(self) -> T {
+            self.0.into_inner()
+        }
+    }
+
+    /// A wrapper around a [`RefCell`] that only allows mutable borrows based on a condition in the resolver.
+    #[derive(Default)]
+    pub(crate) struct CmRefCell<T>(RefCell<T>);
+
+    impl<T> CmRefCell<T> {
+        pub(crate) const fn new(value: T) -> CmRefCell<T> {
+            CmRefCell(RefCell::new(value))
+        }
+
+        #[inline]
+        #[track_caller]
+        pub(crate) fn borrow_mut_unchecked(&self) -> RefMut<'_, T> {
+            self.0.borrow_mut()
+        }
+
+        #[inline]
+        #[track_caller]
+        pub(crate) fn borrow_mut<'ra, 'tcx>(&self, r: &Resolver<'ra, 'tcx>) -> RefMut<'_, T> {
+            if r.assert_speculative {
+                panic!("Not allowed to mutably borrow a CmRefCell during speculative resolution");
+            }
+            self.borrow_mut_unchecked()
+        }
+
+        #[inline]
+        #[track_caller]
+        pub(crate) fn try_borrow_mut_unchecked(&self) -> Result<RefMut<'_, T>, BorrowMutError> {
+            self.0.try_borrow_mut()
+        }
+
+        #[inline]
+        #[track_caller]
+        pub(crate) fn borrow(&self) -> Ref<'_, T> {
+            self.0.borrow()
+        }
+    }
+}
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index d3e98ef839b..c50dfd41b51 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -189,7 +189,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
         let output_macro_rules_scope = self.build_reduced_graph(fragment, parent_scope);
         self.output_macro_rules_scopes.insert(expansion, output_macro_rules_scope);
 
-        parent_scope.module.unexpanded_invocations.borrow_mut().remove(&expansion);
+        parent_scope.module.unexpanded_invocations.borrow_mut(self).remove(&expansion);
         if let Some(unexpanded_invocations) =
             self.impl_unexpanded_invocations.get_mut(&self.invocation_parent(expansion))
         {
diff --git a/library/std/src/sys/stdio/vexos.rs b/library/std/src/sys/stdio/vexos.rs
index 1f2251c6421..9a391feb7a8 100644
--- a/library/std/src/sys/stdio/vexos.rs
+++ b/library/std/src/sys/stdio/vexos.rs
@@ -13,7 +13,7 @@ impl Stdin {
 }
 
 impl io::Read for Stdin {
-    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         let mut count = 0;
 
         for out_byte in buf.iter_mut() {
diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs
index 2083c675e1f..d5b15d79086 100644
--- a/src/bootstrap/src/core/build_steps/clippy.rs
+++ b/src/bootstrap/src/core/build_steps/clippy.rs
@@ -564,6 +564,7 @@ impl Step for CI {
                 "clippy::same_item_push".into(),
                 "clippy::single_char_add_str".into(),
                 "clippy::to_string_in_format_args".into(),
+                "clippy::unconditional_recursion".into(),
             ],
             forbid: vec![],
         };
@@ -591,6 +592,7 @@ impl Step for CI {
                 "clippy::same_item_push".into(),
                 "clippy::single_char_add_str".into(),
                 "clippy::to_string_in_format_args".into(),
+                "clippy::unconditional_recursion".into(),
             ],
             forbid: vec![],
         };
diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index 14104d7d1d7..e699922f4dc 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -1832,8 +1832,9 @@ impl Step for Sysroot {
         let sysroot = sysroot_dir(compiler.stage);
         trace!(stage = ?compiler.stage, ?sysroot);
 
-        builder
-            .verbose(|| println!("Removing sysroot {} to avoid caching bugs", sysroot.display()));
+        builder.do_if_verbose(|| {
+            println!("Removing sysroot {} to avoid caching bugs", sysroot.display())
+        });
         let _ = fs::remove_dir_all(&sysroot);
         t!(fs::create_dir_all(&sysroot));
 
@@ -1902,12 +1903,7 @@ impl Step for Sysroot {
                 if !path.parent().is_none_or(|p| p.ends_with(&suffix)) {
                     return true;
                 }
-                if !filtered_files.iter().all(|f| f != path.file_name().unwrap()) {
-                    builder.verbose_than(1, || println!("ignoring {}", path.display()));
-                    false
-                } else {
-                    true
-                }
+                filtered_files.iter().all(|f| f != path.file_name().unwrap())
             });
         }
 
@@ -2596,7 +2592,7 @@ pub fn stream_cargo(
         cmd.arg(arg);
     }
 
-    builder.verbose(|| println!("running: {cmd:?}"));
+    builder.do_if_verbose(|| println!("running: {cmd:?}"));
 
     let streaming_command = cmd.stream_capture_stdout(&builder.config.exec_ctx);
 
diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs
index 99a1062109a..b79d2cb413d 100644
--- a/src/bootstrap/src/core/build_steps/dist.rs
+++ b/src/bootstrap/src/core/build_steps/dist.rs
@@ -2304,7 +2304,7 @@ fn maybe_install_llvm(
         let mut cmd = command(host_llvm_config);
         cmd.cached();
         cmd.arg("--libfiles");
-        builder.verbose(|| println!("running {cmd:?}"));
+        builder.do_if_verbose(|| println!("running {cmd:?}"));
         let files = cmd.run_capture_stdout(builder).stdout();
         let build_llvm_out = &builder.llvm_out(builder.config.host_target);
         let target_llvm_out = &builder.llvm_out(target);
diff --git a/src/bootstrap/src/core/build_steps/gcc.rs b/src/bootstrap/src/core/build_steps/gcc.rs
index 717dea37e9e..17ab8c4e2f4 100644
--- a/src/bootstrap/src/core/build_steps/gcc.rs
+++ b/src/bootstrap/src/core/build_steps/gcc.rs
@@ -128,7 +128,7 @@ fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option<Pa
         &builder.config,
         builder.config.rust_info.is_managed_git_subrepository(),
     );
-    builder.verbose(|| {
+    builder.do_if_verbose(|| {
         eprintln!("GCC freshness: {source:?}");
     });
     match source {
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index e7f5879b5f5..ca2731819e7 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -582,11 +582,11 @@ impl Miri {
         // We re-use the `cargo` from above.
         cargo.arg("--print-sysroot");
 
-        builder.verbose(|| println!("running: {cargo:?}"));
+        builder.do_if_verbose(|| println!("running: {cargo:?}"));
         let stdout = cargo.run_capture_stdout(builder).stdout();
         // Output is "<sysroot>\n".
         let sysroot = stdout.trim_end();
-        builder.verbose(|| println!("`cargo miri setup --print-sysroot` said: {sysroot:?}"));
+        builder.do_if_verbose(|| println!("`cargo miri setup --print-sysroot` said: {sysroot:?}"));
         PathBuf::from(sysroot)
     }
 }
@@ -2675,7 +2675,7 @@ fn markdown_test(builder: &Builder<'_>, compiler: Compiler, markdown: &Path) ->
         return true;
     }
 
-    builder.verbose(|| println!("doc tests for: {}", markdown.display()));
+    builder.do_if_verbose(|| println!("doc tests for: {}", markdown.display()));
     let mut cmd = builder.rustdoc_cmd(compiler);
     builder.add_rust_test_threads(&mut cmd);
     // allow for unstable options such as new editions
diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs
index ee2bb710674..9fc4ce669c2 100644
--- a/src/bootstrap/src/core/builder/cargo.rs
+++ b/src/bootstrap/src/core/builder/cargo.rs
@@ -1139,7 +1139,7 @@ impl Builder<'_> {
             cargo.env("RUSTC_BACKTRACE_ON_ICE", "1");
         }
 
-        if self.is_verbose_than(1) {
+        if self.verbosity >= 2 {
             // This provides very useful logs especially when debugging build cache-related stuff.
             cargo.env("CARGO_LOG", "cargo::core::compiler::fingerprint=info");
         }
diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs
index 8226b4325b6..049d2647bec 100644
--- a/src/bootstrap/src/core/builder/mod.rs
+++ b/src/bootstrap/src/core/builder/mod.rs
@@ -545,7 +545,7 @@ impl StepDescription {
         if !builder.config.skip.is_empty()
             && !matches!(builder.config.get_dry_run(), DryRun::SelfCheck)
         {
-            builder.verbose(|| {
+            builder.do_if_verbose(|| {
                 println!(
                     "{:?} not skipped for {:?} -- not in {:?}",
                     pathset, self.name, builder.config.skip
@@ -947,7 +947,7 @@ impl Step for Libdir {
             // Sysroot`).
             if !builder.download_rustc() {
                 let sysroot_target_libdir = sysroot.join(self.target).join("lib");
-                builder.verbose(|| {
+                builder.do_if_verbose(|| {
                     eprintln!(
                         "Removing sysroot {} to avoid caching bugs",
                         sysroot_target_libdir.display()
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index dd2d5a1fd53..fb7c334491b 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -1571,8 +1571,8 @@ impl Config {
     }
 
     /// Runs a function if verbosity is greater than 0
-    pub fn verbose(&self, f: impl Fn()) {
-        self.exec_ctx.verbose(f);
+    pub fn do_if_verbose(&self, f: impl Fn()) {
+        self.exec_ctx.do_if_verbose(f);
     }
 
     pub fn any_sanitizers_to_build(&self) -> bool {
@@ -2061,7 +2061,7 @@ pub fn download_ci_rustc_commit<'a>(
         // Look for a version to compare to based on the current commit.
         // Only commits merged by bors will have CI artifacts.
         let freshness = check_path_modifications_(dwn_ctx, RUSTC_IF_UNCHANGED_ALLOWED_PATHS);
-        dwn_ctx.exec_ctx.verbose(|| {
+        dwn_ctx.exec_ctx.do_if_verbose(|| {
             eprintln!("rustc freshness: {freshness:?}");
         });
         match freshness {
diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs
index 2f3c80559c0..37871f0fe1e 100644
--- a/src/bootstrap/src/core/download.rs
+++ b/src/bootstrap/src/core/download.rs
@@ -106,7 +106,7 @@ enum DownloadSource {
 /// Functions that are only ever called once, but named for clarity and to avoid thousand-line functions.
 impl Config {
     pub(crate) fn download_clippy(&self) -> PathBuf {
-        self.verbose(|| println!("downloading stage0 clippy artifacts"));
+        self.do_if_verbose(|| println!("downloading stage0 clippy artifacts"));
 
         let date = &self.stage0_metadata.compiler.date;
         let version = &self.stage0_metadata.compiler.version;
@@ -151,7 +151,9 @@ impl Config {
     }
 
     pub(crate) fn download_ci_rustc(&self, commit: &str) {
-        self.verbose(|| println!("using downloaded stage2 artifacts from CI (commit {commit})"));
+        self.do_if_verbose(|| {
+            println!("using downloaded stage2 artifacts from CI (commit {commit})")
+        });
 
         let version = self.artifact_version_part(commit);
         // download-rustc doesn't need its own cargo, it can just use beta's. But it does need the
@@ -258,7 +260,7 @@ impl Config {
         let llvm_root = self.ci_llvm_root();
         let llvm_freshness =
             detect_llvm_freshness(self, self.rust_info.is_managed_git_subrepository());
-        self.verbose(|| {
+        self.do_if_verbose(|| {
             eprintln!("LLVM freshness: {llvm_freshness:?}");
         });
         let llvm_sha = match llvm_freshness {
@@ -557,7 +559,7 @@ pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a
 #[cfg(not(test))]
 pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>, out: &Path) {
     let dwn_ctx = dwn_ctx.as_ref();
-    dwn_ctx.exec_ctx.verbose(|| {
+    dwn_ctx.exec_ctx.do_if_verbose(|| {
         println!("downloading stage0 beta artifacts");
     });
 
@@ -812,7 +814,7 @@ fn download_component<'a>(
                 unpack(dwn_ctx.exec_ctx, &tarball, &bin_root, prefix);
                 return;
             } else {
-                dwn_ctx.exec_ctx.verbose(|| {
+                dwn_ctx.exec_ctx.do_if_verbose(|| {
                     println!(
                         "ignoring cached file {} due to failed verification",
                         tarball.display()
@@ -853,7 +855,7 @@ download-rustc = false
 pub(crate) fn verify(exec_ctx: &ExecutionContext, path: &Path, expected: &str) -> bool {
     use sha2::Digest;
 
-    exec_ctx.verbose(|| {
+    exec_ctx.do_if_verbose(|| {
         println!("verifying {}", path.display());
     });
 
@@ -934,7 +936,7 @@ fn unpack(exec_ctx: &ExecutionContext, tarball: &Path, dst: &Path, pattern: &str
         short_path = short_path.strip_prefix(pattern).unwrap_or(short_path);
         let dst_path = dst.join(short_path);
 
-        exec_ctx.verbose(|| {
+        exec_ctx.do_if_verbose(|| {
             println!("extracting {} to {}", original_path.display(), dst.display());
         });
 
@@ -965,7 +967,7 @@ fn download_file<'a>(
 ) {
     let dwn_ctx = dwn_ctx.as_ref();
 
-    dwn_ctx.exec_ctx.verbose(|| {
+    dwn_ctx.exec_ctx.do_if_verbose(|| {
         println!("download {url}");
     });
     // Use a temporary file in case we crash while downloading, to avoid a corrupt download in cache/.
diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index e953fe2945e..4f4d35673d5 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -415,7 +415,7 @@ macro_rules! forward {
 }
 
 forward! {
-    verbose(f: impl Fn()),
+    do_if_verbose(f: impl Fn()),
     is_verbose() -> bool,
     create(path: &Path, s: &str),
     remove(f: &Path),
@@ -601,11 +601,11 @@ impl Build {
             .unwrap()
             .trim();
         if local_release.split('.').take(2).eq(version.split('.').take(2)) {
-            build.verbose(|| println!("auto-detected local-rebuild {local_release}"));
+            build.do_if_verbose(|| println!("auto-detected local-rebuild {local_release}"));
             build.local_rebuild = true;
         }
 
-        build.verbose(|| println!("finding compilers"));
+        build.do_if_verbose(|| println!("finding compilers"));
         utils::cc_detect::fill_compilers(&mut build);
         // When running `setup`, the profile is about to change, so any requirements we have now may
         // be different on the next invocation. Don't check for them until the next time x.py is
@@ -613,7 +613,7 @@ impl Build {
         //
         // Similarly, for `setup` we don't actually need submodules or cargo metadata.
         if !matches!(build.config.cmd, Subcommand::Setup { .. }) {
-            build.verbose(|| println!("running sanity check"));
+            build.do_if_verbose(|| println!("running sanity check"));
             crate::core::sanity::check(&mut build);
 
             // Make sure we update these before gathering metadata so we don't get an error about missing
@@ -631,7 +631,7 @@ impl Build {
             // Now, update all existing submodules.
             build.update_existing_submodules();
 
-            build.verbose(|| println!("learning about cargo"));
+            build.do_if_verbose(|| println!("learning about cargo"));
             crate::core::metadata::build(&mut build);
         }
 
@@ -1087,18 +1087,6 @@ impl Build {
         })
     }
 
-    /// Check if verbosity is greater than the `level`
-    pub fn is_verbose_than(&self, level: usize) -> bool {
-        self.verbosity > level
-    }
-
-    /// Runs a function if verbosity is greater than `level`.
-    fn verbose_than(&self, level: usize, f: impl Fn()) {
-        if self.is_verbose_than(level) {
-            f()
-        }
-    }
-
     fn info(&self, msg: &str) {
         match self.config.get_dry_run() {
             DryRun::SelfCheck => (),
@@ -1816,7 +1804,6 @@ impl Build {
         if self.config.dry_run() {
             return;
         }
-        self.verbose_than(1, || println!("Copy/Link {src:?} to {dst:?}"));
         if src == dst {
             return;
         }
@@ -1933,7 +1920,10 @@ impl Build {
             return;
         }
         let dst = dstdir.join(src.file_name().unwrap());
-        self.verbose_than(1, || println!("Install {src:?} to {dst:?}"));
+
+        #[cfg(feature = "tracing")]
+        let _span = trace_io!("install", ?src, ?dst);
+
         t!(fs::create_dir_all(dstdir));
         if !src.exists() {
             panic!("ERROR: File \"{}\" not found!", src.display());
diff --git a/src/bootstrap/src/utils/build_stamp.rs b/src/bootstrap/src/utils/build_stamp.rs
index 4c35388a181..5cd68f6d4fe 100644
--- a/src/bootstrap/src/utils/build_stamp.rs
+++ b/src/bootstrap/src/utils/build_stamp.rs
@@ -112,7 +112,7 @@ pub fn clear_if_dirty(builder: &Builder<'_>, dir: &Path, input: &Path) -> bool {
     let stamp = BuildStamp::new(dir);
     let mut cleared = false;
     if mtime(stamp.path()) < mtime(input) {
-        builder.verbose(|| println!("Dirty - {}", dir.display()));
+        builder.do_if_verbose(|| println!("Dirty - {}", dir.display()));
         let _ = fs::remove_dir_all(dir);
         cleared = true;
     } else if stamp.path().exists() {
diff --git a/src/bootstrap/src/utils/cc_detect.rs b/src/bootstrap/src/utils/cc_detect.rs
index d3926df9650..0662ae304ac 100644
--- a/src/bootstrap/src/utils/cc_detect.rs
+++ b/src/bootstrap/src/utils/cc_detect.rs
@@ -137,16 +137,16 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) {
         build.cxx.insert(target, compiler);
     }
 
-    build.verbose(|| println!("CC_{} = {:?}", target.triple, build.cc(target)));
-    build.verbose(|| println!("CFLAGS_{} = {cflags:?}", target.triple));
+    build.do_if_verbose(|| println!("CC_{} = {:?}", target.triple, build.cc(target)));
+    build.do_if_verbose(|| println!("CFLAGS_{} = {cflags:?}", target.triple));
     if let Ok(cxx) = build.cxx(target) {
         let mut cxxflags = build.cc_handled_clags(target, CLang::Cxx);
         cxxflags.extend(build.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::Cxx));
-        build.verbose(|| println!("CXX_{} = {cxx:?}", target.triple));
-        build.verbose(|| println!("CXXFLAGS_{} = {cxxflags:?}", target.triple));
+        build.do_if_verbose(|| println!("CXX_{} = {cxx:?}", target.triple));
+        build.do_if_verbose(|| println!("CXXFLAGS_{} = {cxxflags:?}", target.triple));
     }
     if let Some(ar) = ar {
-        build.verbose(|| println!("AR_{} = {ar:?}", target.triple));
+        build.do_if_verbose(|| println!("AR_{} = {ar:?}", target.triple));
         build.ar.insert(target, ar);
     }
 
diff --git a/src/bootstrap/src/utils/exec.rs b/src/bootstrap/src/utils/exec.rs
index e09f3086b77..f875e6e1af7 100644
--- a/src/bootstrap/src/utils/exec.rs
+++ b/src/bootstrap/src/utils/exec.rs
@@ -630,7 +630,7 @@ impl ExecutionContext {
         &self.dry_run
     }
 
-    pub fn verbose(&self, f: impl Fn()) {
+    pub fn do_if_verbose(&self, f: impl Fn()) {
         if self.is_verbose() {
             f()
         }
@@ -686,7 +686,7 @@ impl ExecutionContext {
 
         if let Some(cached_output) = self.command_cache.get(&fingerprint) {
             command.mark_as_executed();
-            self.verbose(|| println!("Cache hit: {command:?}"));
+            self.do_if_verbose(|| println!("Cache hit: {command:?}"));
             self.profiler.record_cache_hit(fingerprint);
             return DeferredCommand { state: CommandState::Cached(cached_output) };
         }
@@ -713,7 +713,7 @@ impl ExecutionContext {
             };
         }
 
-        self.verbose(|| {
+        self.do_if_verbose(|| {
             println!("running: {command:?} (created at {created_at}, executed at {executed_at})")
         });
 
diff --git a/src/bootstrap/src/utils/render_tests.rs b/src/bootstrap/src/utils/render_tests.rs
index 90fd57d976d..e90a7ef4232 100644
--- a/src/bootstrap/src/utils/render_tests.rs
+++ b/src/bootstrap/src/utils/render_tests.rs
@@ -48,7 +48,7 @@ pub(crate) fn try_run_tests(
 }
 
 fn run_tests(builder: &Builder<'_>, cmd: &mut BootstrapCommand, stream: bool) -> bool {
-    builder.verbose(|| println!("running: {cmd:?}"));
+    builder.do_if_verbose(|| println!("running: {cmd:?}"));
 
     let Some(mut streaming_command) = cmd.stream_capture_stdout(&builder.config.exec_ctx) else {
         return true;
diff --git a/src/bootstrap/src/utils/tarball.rs b/src/bootstrap/src/utils/tarball.rs
index 7b77b212934..079afb7a005 100644
--- a/src/bootstrap/src/utils/tarball.rs
+++ b/src/bootstrap/src/utils/tarball.rs
@@ -356,7 +356,7 @@ impl<'a> Tarball<'a> {
 
         // For `x install` tarball files aren't needed, so we can speed up the process by not producing them.
         let compression_profile = if self.builder.kind == Kind::Install {
-            self.builder.verbose(|| {
+            self.builder.do_if_verbose(|| {
                 println!("Forcing dist.compression-profile = 'no-op' for `x install`.")
             });
             // "no-op" indicates that the rust-installer won't produce compressed tarball sources.
diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md
index e4bf33dd8a0..619eebd15bd 100644
--- a/src/doc/rustc/src/SUMMARY.md
+++ b/src/doc/rustc/src/SUMMARY.md
@@ -52,7 +52,6 @@
     - [aarch64_be-unknown-none-softfloat](platform-support/aarch64_be-unknown-none-softfloat.md)
     - [aarch64_be-unknown-linux-musl](platform-support/aarch64_be-unknown-linux-musl.md)
     - [amdgcn-amd-amdhsa](platform-support/amdgcn-amd-amdhsa.md)
-    - [armeb-unknown-linux-gnueabi](platform-support/armeb-unknown-linux-gnueabi.md)
     - [arm-none-eabi](platform-support/arm-none-eabi.md)
       - [armv4t-none-eabi](platform-support/armv4t-none-eabi.md)
       - [armv5te-none-eabi](platform-support/armv5te-none-eabi.md)
@@ -65,12 +64,14 @@
       - [thumbv7m-none-eabi](./platform-support/thumbv7m-none-eabi.md)
       - [thumbv8m.base-none-eabi](./platform-support/thumbv8m.base-none-eabi.md)
       - [thumbv8m.main-none-eabi\*](./platform-support/thumbv8m.main-none-eabi.md)
-    - [armv5te-unknown-linux-gnueabi](platform-support/armv5te-unknown-linux-gnueabi.md)
+    - [arm\*-unknown-linux-\*](./platform-support/arm-linux.md)
+        - [armeb-unknown-linux-gnueabi](platform-support/armeb-unknown-linux-gnueabi.md)
+        - [armv5te-unknown-linux-gnueabi](platform-support/armv5te-unknown-linux-gnueabi.md)
+        - [armv7-unknown-linux-uclibceabi](platform-support/armv7-unknown-linux-uclibceabi.md)
+        - [armv7-unknown-linux-uclibceabihf](platform-support/armv7-unknown-linux-uclibceabihf.md)
     - [armv6k-nintendo-3ds](platform-support/armv6k-nintendo-3ds.md)
     - [armv7-rtems-eabihf](platform-support/armv7-rtems-eabihf.md)
     - [armv7-sony-vita-newlibeabihf](platform-support/armv7-sony-vita-newlibeabihf.md)
-    - [armv7-unknown-linux-uclibceabi](platform-support/armv7-unknown-linux-uclibceabi.md)
-    - [armv7-unknown-linux-uclibceabihf](platform-support/armv7-unknown-linux-uclibceabihf.md)
     - [armv7a-vex-v5](platform-support/armv7a-vex-v5.md)
     - [\*-android and \*-androideabi](platform-support/android.md)
     - [\*-linux-ohos](platform-support/openharmony.md)
diff --git a/src/doc/rustc/src/platform-support/arm-linux.md b/src/doc/rustc/src/platform-support/arm-linux.md
new file mode 100644
index 00000000000..5f40743f3d0
--- /dev/null
+++ b/src/doc/rustc/src/platform-support/arm-linux.md
@@ -0,0 +1,217 @@
+# Arm Linux support in Rust
+
+The Arm Architecture has been around since the mid-1980s, going through nine
+major revisions, many minor revisions, and spanning both 32-bith and 64-bit
+architectures. This page covers 32-bit Arm platforms that run some form of
+Linux (but not Android). Those targets are:
+
+* `arm-unknown-linux-gnueabi`
+* `arm-unknown-linux-gnueabihf`
+* `arm-unknown-linux-musleabi`
+* `arm-unknown-linux-musleabihf`
+* [`armeb-unknown-linux-gnueabi`](armeb-unknown-linux-gnueabi.md)
+* `armv4t-unknown-linux-gnueabi`
+* [`armv5te-unknown-linux-gnueabi`](armv5te-unknown-linux-gnueabi.md)
+* `armv5te-unknown-linux-musleabi`
+* `armv5te-unknown-linux-uclibceabi`
+* `armv7-unknown-linux-gnueabi`
+* `armv7-unknown-linux-gnueabihf`
+* `armv7-unknown-linux-musleabi`
+* `armv7-unknown-linux-musleabihf`
+* `armv7-unknown-linux-ohos`
+* [`armv7-unknown-linux-uclibceabi`](armv7-unknown-linux-uclibceabi.md)
+* [`armv7-unknown-linux-uclibceabihf`](armv7-unknown-linux-uclibceabihf.md)
+* `thumbv7neon-unknown-linux-gnueabihf`
+* `thumbv7neon-unknown-linux-musleabihf`
+
+Some of these targets have dedicated pages and some do not. This is largely
+due to historical accident, or the enthusiasm of the maintainers. This
+document attempts to cover all the targets, but only in broad terms.
+
+To make sense of this list, the architecture and ABI component of the
+`<architecture>-unknown-linux-<abi>` tuple will be discussed separately.
+
+The second part of the tuple is `unknown` because these systems don't come
+from any one specific vendor (like `powerpc-ibm-aix` or
+`aarch64-apple-darwin`). The third part is `linux`, because this page only
+discusses Linux targets.
+
+## Architecture Component
+
+* `arm`
+* `armeb`
+* `armv4t`
+* `armv5te`
+* `armv7`
+* `thumbv7neon`
+
+The architecture component simply called `arm` corresponds to the Armv6
+architecture - that is, version 6 of the Arm Architecture as defined in
+version 6 of the Arm Architecture Reference Manual (the Arm ARM). This was the
+last 'legacy' release of the Arm architecture, before they split into
+Application, Real-Time and Microcontroller profiles (leading to Armv7-A,
+Armv7-R and Armv7-M). Processors that implement the Armv6 architecture include
+the ARM1176JZF-S, as found in BCM2835 SoC that powers the Raspberry Pi Zero.
+Arm processors are generally fairly backwards compatible, especially for
+user-mode code, so code compiled for the `arm` architecture should also work
+on newer ARMv7-A systems, or even 64/32-bit Armv8-A systems.
+
+The `armeb` architecture component specifies an Armv6 processor running in Big
+Endian mode (`eb` is for big-endian - the letters are backwards because
+engineers used to little-endian systems perceive big-endian numbers to be
+written into memory backwards, and they thought it was funnier like that).
+Most Arm processors can operate in either little-endian or big-endian mode and
+little-endian mode is by far the most common. However, if for whatever reason
+you wish to store your Most Significant Bytes first, these targets are
+available. They just aren't terribly well tested, or compatible with most
+existing pre-compiled Arm libraries.
+
+Targets that start with `armv4t` are for processors implementing the Armv4T
+architecture from 1994. These include the ARM7TDMI, as found in the Nokia 6110
+brick-phone and the Game Boy Advance. The 'T' stands for *Thumb* and indicate
+that the processors can execute smaller 16-bit versions of some of the 32-bit
+Arm instructions. Because a Thumb is like a small version of an Arm.
+
+Targets that start with `armv5te` are for processors implementing the Armv5TE
+architecture. These are mostly from the ARM9 family, like the ARM946E-S found
+in the Nintendo DS. If you are programming an Arm machine from the early
+2000s, this might be what you need.
+
+The `armv7` is arguably a misnomer, and it should be `armv7a`. This is because
+it corresponds to the Application profile of Armv7 (i.e. Armv7-A), as opposed
+to the Real-Time or Microcontroller profile. Processors implementing this
+architecture include the Cortex-A7 and Cortex-A8.
+
+The `thumbv7neon` component indicates support for a processor that implements
+ARMv7-A (the same as `armv7`), it generates Thumb instructions (technically
+Thumb-2, also known as the T32 ISA) as opposed to Arm instructions (also known
+as the A32 ISA). These instructions are smaller, giving more code per KB of
+RAM, but may have a performance penalty if they take two instructions to do
+something Arm instructions could do in one. It's a complex trade-off and you
+should be doing benchmarks to work out which is better for you, if you
+strongly care about code size and/or performance. This component also enables
+support for Arm's SIMD extensions, known as Neon. These extensions will
+improve performance for certain kinds of repetitive operations.
+
+## ABI Component
+
+* `gnueabi`
+* `gnueabihf`
+* `musleabi`
+* `musleabihf`
+* `ohos`
+* `uclibceabi`
+* `uclibceabihf`
+
+You will need to select the appropriate ABI to match the system you want to be
+running this code on. For example, running `eabihf` code on an `eabi` system
+will not work correctly.
+
+The `gnueabi` ABI component indicates support for using the GNU C Library
+(glibc), and the Arm Embedded ABI (EABI). The EABI is a replacement for the
+original ABI (now called the Old ABI or OABI), and it is the standard ABI for
+32-bit Arm systems. With this ABI, function parameters that are `f32` or `f64`
+are passed as if they were integers, instead of being passed via in FPU
+registers. Generally these targets also disable the use of the FPU entirely,
+although that isn't always true.
+
+The `gnueabihf` ABI component is like `gnueabi`, except that it support the
+'hard-float' of the EABI. That is, function parameters that are `f32` or `f64`
+are passed in FPU registers. Naturally, this makes the FPU mandatory.
+
+Most 'desktop' Linux distributions (Debian, Ubuntu, Fedora, etc) use the GNU C
+Library and so you should probably select either `gnueabi` or `gnueabihf`,
+depending on whether your distribution is using 'soft-float' (EABI) or
+'hard-float' (EABIHF). Debian happens to offer
+[both](https://wiki.debian.org/ArmEabiPort)
+[kinds](https://wiki.debian.org/ArmHardFloatPort).
+
+The `musleabi` and `musleabihf` ABI components offer support for the [musl C
+library](https://musl.libc.org/). This C library can be used to create 'static
+binaries' that have no run-time library requirements (a feature that glibc
+does not support). There are soft-float (`eabi`) and hard-float (`eabihf`)
+variants, as per the `gnu*` targets above.
+
+The `uclibceabi` and `uclibceabihf` ABI components are for the [uClibc-ng C
+library](https://uclibc-ng.org/). This is sometimes used in light-weight
+embedded Linux distributions, like those created with
+[buildroot](https://www.buildroot.org/).
+
+## Cross Compilation
+
+Unfortunately, 32-bit Arm machines are generally not the fastest around, and
+they don't have much RAM. This means you are likely to be cross-compiling.
+
+To do this, you need to give Rust a suitable linker to use - one that knows
+the Arm architecture, and more importantly, knows where to find a suitable C
+Library to link against.
+
+To do that, you can add the `linker` property to your `.cargo/config.toml`.
+Typically you would refer to a suitable copy of GCC that has built as a
+cross-compiler, alongside a C library.
+
+```toml
+[target.arm-unknown-linux-gnueabi]
+linker = "arm-linux-gnueabi-gcc"
+```
+
+On Debian Linux, you could install such a cross-compilation toolchain with
+`apt install gcc-arm-linux-gnueabi`. For more exotic combinations, you might
+need to build a bespoke version of GCC using [crosstool-ng].
+
+[crosstool-ng]: https://github.com/crosstool-ng/crosstool-ng
+
+Note that for GCC, all 32-bit Arm architectures are handled in the same build
+- there are no separate Armv4T or Armv6 builds of GCC. The architecture is
+selected with flags, like `-march=armv6`, but they aren't required for the
+linker.
+
+Let's assume we are on some Debian machine, and we want to build a basic Arm
+Linux binary for a distribution using the GNU C Library, targeting Armv6 with
+a hard-float ABI. Such a binary should work on a Raspberry Pi, for example.
+The commands are:
+
+```bash
+sudo apt install -y gcc-arm-linux-gnueabihf
+rustup target add arm-unknown-linux-gnueabihf
+cargo new --bin armdemo
+cd armdemo
+mkdir .cargo
+cat > .cargo/config.toml << EOF
+[target.arm-unknown-linux-gnueabihf]
+linker = "arm-linux-gnueabihf-gcc"
+EOF
+cargo build --target=arm-unknown-linux-gnueabihf
+```
+
+This will give us our ARM Linux binary for the GNU C Library with a soft-float ABI:
+
+```console
+$ file ./target/arm-unknown-linux-gnueabi/debug/armdemo
+./target/arm-unknown-linux-gnueabi/debug/armdemo: ELF 32-bit LSB pie
+  executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter
+  /lib/ld-linux.so.3, BuildID[sha1]=dd0b9aa5ae876330fd4e2fcf393850f083ec7fcd,
+  for GNU/Linux 3.2.0, with debug_info, not stripped
+```
+
+If you are building C code as part of your Rust project, you may want to
+direct `cc-rs` to use an appropriate cross-compiler with the `CROSS_COMPILE`
+environment variable. You may also want to set the CFLAGS environment variable
+for the target. For example:
+
+```bash
+export CROSS_COMPILE=arm-linux-gnueabi
+export CFLAGS_arm_unknown_linux_gnueabi="-march=armv6"
+```
+
+(Note that the dashes (`-`) turn to underscores (`_`) to form the name of the
+CFLAGS environment variable)
+
+If you are building for a Tier 3 target using `-Zbuild-std` (on Nightly Rust),
+you need to set these variables as well:
+
+```bash
+export CXX_arm_unknown_linux_gnueabi=arm-linux-gnueabi-g++
+export CC_arm_unknown_linux_gnueabi=arm-linux-gnueabi-gcc
+cargo +nightly build -Zbuild-std --target=arm-unknown-linux-gnueabi
+```
diff --git a/src/doc/rustc/src/platform-support/armeb-unknown-linux-gnueabi.md b/src/doc/rustc/src/platform-support/armeb-unknown-linux-gnueabi.md
index 7c1c5db7076..cd0623f73a1 100644
--- a/src/doc/rustc/src/platform-support/armeb-unknown-linux-gnueabi.md
+++ b/src/doc/rustc/src/platform-support/armeb-unknown-linux-gnueabi.md
@@ -3,6 +3,9 @@
 
 Target for cross-compiling Linux user-mode applications targeting the Arm BE8 architecture.
 
+See [`arm-linux`](arm-linux.md) for information applicable to all Arm Linux
+targets.
+
 ## Overview
 BE8 architecture retains the same little-endian ordered code-stream used by conventional little endian Arm systems, however the data accesses are in big-endian. BE8 is used primarily in high-performance networking applications where the ability to read packets in their native "Network Byte Order" is important (many network protocols transmit data in big-endian byte order for their wire formats).
 
diff --git a/src/doc/rustc/src/platform-support/armv5te-unknown-linux-gnueabi.md b/src/doc/rustc/src/platform-support/armv5te-unknown-linux-gnueabi.md
index 0aebbc34d40..a924f476411 100644
--- a/src/doc/rustc/src/platform-support/armv5te-unknown-linux-gnueabi.md
+++ b/src/doc/rustc/src/platform-support/armv5te-unknown-linux-gnueabi.md
@@ -5,6 +5,9 @@
 This target supports Linux programs with glibc on ARMv5TE CPUs without
 floating-point units.
 
+See [`arm-linux`](arm-linux.md) for information applicable to all Arm Linux
+targets.
+
 ## Target maintainers
 
 There are currently no formally documented target maintainers.
diff --git a/src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabi.md b/src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabi.md
index e553c49589d..4ab0a07090a 100644
--- a/src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabi.md
+++ b/src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabi.md
@@ -4,6 +4,9 @@
 
 This target supports Armv7-A softfloat CPUs and uses the uclibc-ng standard library. This is a common configuration on many consumer routers (e.g., Netgear R7000, Asus RT-AC68U).
 
+See [`arm-linux`](arm-linux.md) for information applicable to all Arm Linux
+targets.
+
 ## Target maintainers
 
 [@lancethepants](https://github.com/lancethepants)
diff --git a/src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabihf.md b/src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabihf.md
index 91f3ea886cc..9fb24906b4f 100644
--- a/src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabihf.md
+++ b/src/doc/rustc/src/platform-support/armv7-unknown-linux-uclibceabihf.md
@@ -4,6 +4,9 @@
 
 This tier supports the Armv7-A processor running a Linux kernel and uClibc-ng standard library.  It provides full support for rust and the rust standard library.
 
+See [`arm-linux`](arm-linux.md) for information applicable to all Arm Linux
+targets.
+
 ## Target Maintainers
 
 [@skrap](https://github.com/skrap)
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 9a14137a6e8..d06540a65b5 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -603,7 +603,12 @@ impl AllTypes {
         }
 
         fmt::from_fn(|f| {
-            f.write_str("<h1>List of all items</h1>")?;
+            f.write_str(
+                "<div class=\"main-heading\">\
+                    <h1>List of all items</h1>\
+                    <rustdoc-toolbar></rustdoc-toolbar>\
+                </div>",
+            )?;
             // Note: print_entries does not escape the title, because we know the current set of titles
             // doesn't require escaping.
             print_entries(&self.structs, ItemSection::Structs).fmt(f)?;
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index e37a5246a76..3a1db805d01 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -386,8 +386,13 @@ impl CratesIndexPart {
         let layout = &cx.shared.layout;
         let style_files = &cx.shared.style_files;
         const DELIMITER: &str = "\u{FFFC}"; // users are being naughty if they have this
-        let content =
-            format!("<h1>List of all crates</h1><ul class=\"all-items\">{DELIMITER}</ul>");
+        let content = format!(
+            "<div class=\"main-heading\">\
+                <h1>List of all crates</h1>\
+                <rustdoc-toolbar></rustdoc-toolbar>\
+            </div>\
+            <ul class=\"all-items\">{DELIMITER}</ul>"
+        );
         let template = layout::render(layout, &page, "", content, style_files);
         SortedTemplate::from_template(&template, DELIMITER)
             .expect("Object Replacement Character (U+FFFC) should not appear in the --index-page")
diff --git a/tests/rustdoc-gui/search-title.goml b/tests/rustdoc-gui/search-title.goml
index 83321a05f2b..5808ed845a3 100644
--- a/tests/rustdoc-gui/search-title.goml
+++ b/tests/rustdoc-gui/search-title.goml
@@ -20,3 +20,15 @@ assert-document-property: {"title": '"another one" Search - Rust'}
 press-key: "Escape"
 
 assert-document-property: {"title": |title|}
+
+// check that all.html does it correctly, too.
+go-to: "file://" + |DOC_PATH| + "/test_docs/all.html"
+assert-document-property: {"title": "List of all items in this crate"}
+call-function: ("perform-search", {"query": "verify"})
+assert-document-property: {"title": '"verify" Search - Rust'}
+
+// check that index.html does it correctly, too.
+go-to: "file://" + |DOC_PATH| + "/index.html"
+assert-document-property: {"title": "Index of crates"}
+call-function: ("perform-search", {"query": "verify"})
+assert-document-property: {"title": '"verify" Search - Rust'}
diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs
index de7c89a9fa3..c0771583ab6 100644
--- a/tests/rustdoc-gui/src/test_docs/lib.rs
+++ b/tests/rustdoc-gui/src/test_docs/lib.rs
@@ -1,3 +1,4 @@
+//@ compile-flags: --enable-index-page -Z unstable-options
 //! The point of this crate is to be able to have enough different "kinds" of
 //! documentation generated so we can test each different features.
 #![doc(html_playground_url="https://play.rust-lang.org/")]