about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2025-02-16 08:38:29 +0100
committerRalf Jung <post@ralfj.de>2025-02-16 08:38:29 +0100
commit703154d4f0db2754493c2f5a464d389377dc0dd2 (patch)
tree042be607aed8e214e257b43911b93b9b142adddc /src
parentacdf9133aca8c5684a7c16f0c7fa0a72cac6a4a3 (diff)
parentd0e7bfd2056cffc7ea0e5f7ed577e987a627ba04 (diff)
downloadrust-703154d4f0db2754493c2f5a464d389377dc0dd2.tar.gz
rust-703154d4f0db2754493c2f5a464d389377dc0dd2.zip
Merge from rustc
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/mk/Makefile.in18
-rw-r--r--src/bootstrap/src/core/builder/tests.rs27
-rw-r--r--src/ci/github-actions/jobs.yml24
-rwxr-xr-xsrc/ci/scripts/upload-artifacts.sh15
-rw-r--r--src/doc/rustc-dev-guide/src/tests/directives.md1
-rw-r--r--src/librustdoc/clean/cfg.rs2
-rw-r--r--src/librustdoc/display.rs (renamed from src/librustdoc/joined.rs)18
-rw-r--r--src/librustdoc/html/format.rs47
-rw-r--r--src/librustdoc/html/render/context.rs29
-rw-r--r--src/librustdoc/html/render/mod.rs96
-rw-r--r--src/librustdoc/html/render/print_item.rs58
-rw-r--r--src/librustdoc/html/static/js/main.js5
-rw-r--r--src/librustdoc/html/static/js/search.js5
-rw-r--r--src/librustdoc/lib.rs2
m---------src/tools/cargo0
-rw-r--r--src/tools/compiletest/src/common.rs13
-rw-r--r--src/tools/compiletest/src/directive-list.rs2
-rw-r--r--src/tools/compiletest/src/header/cfg.rs8
-rw-r--r--src/tools/compiletest/src/header/tests.rs14
-rw-r--r--src/tools/generate-windows-sys/Cargo.toml2
-rw-r--r--src/tools/generate-windows-sys/src/main.rs3
-rw-r--r--src/tools/miri/src/lib.rs1
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs8
-rw-r--r--src/tools/miri/tests/pass/float.rs6
-rw-r--r--src/tools/tidy/src/deps.rs1
-rw-r--r--src/version2
26 files changed, 278 insertions, 129 deletions
diff --git a/src/bootstrap/mk/Makefile.in b/src/bootstrap/mk/Makefile.in
index 7e6a39a236e..88aa70d4f2f 100644
--- a/src/bootstrap/mk/Makefile.in
+++ b/src/bootstrap/mk/Makefile.in
@@ -99,16 +99,18 @@ prepare:
 
 # Set of tests that represent around half of the time of the test suite.
 # Used to split tests across multiple CI runners.
-STAGE_2_TEST_SET1 := test --stage 2 --skip=compiler --skip=src
-STAGE_2_TEST_SET2 := test --stage 2 --skip=tests --skip=coverage-map --skip=coverage-run --skip=library --skip=tidyselftest
+SKIP_COMPILER := --skip=compiler
+SKIP_SRC := --skip=src
+TEST_SET1 := $(SKIP_COMPILER) $(SKIP_SRC)
+TEST_SET2 := --skip=tests --skip=coverage-map --skip=coverage-run --skip=library --skip=tidyselftest
 
 ## MSVC native builders
 
 # this intentionally doesn't use `$(BOOTSTRAP)` so we can test the shebang on Windows
 ci-msvc-py:
-	$(Q)$(CFG_SRC_DIR)/x.py $(STAGE_2_TEST_SET1)
+	$(Q)$(CFG_SRC_DIR)/x.py test --stage 2 $(TEST_SET1)
 ci-msvc-ps1:
-	$(Q)$(CFG_SRC_DIR)/x.ps1 $(STAGE_2_TEST_SET2)
+	$(Q)$(CFG_SRC_DIR)/x.ps1 test --stage 2 $(TEST_SET2)
 ci-msvc: ci-msvc-py ci-msvc-ps1
 
 ## MingW native builders
@@ -116,10 +118,14 @@ ci-msvc: ci-msvc-py ci-msvc-ps1
 # Set of tests that should represent half of the time of the test suite.
 # Used to split tests across multiple CI runners.
 # Test both x and bootstrap entrypoints.
+ci-mingw-x-1:
+	$(Q)$(CFG_SRC_DIR)/x test --stage 2 $(SKIP_COMPILER) $(TEST_SET2)
+ci-mingw-x-2:
+	$(Q)$(CFG_SRC_DIR)/x test --stage 2 $(SKIP_SRC) $(TEST_SET2)
 ci-mingw-x:
-	$(Q)$(CFG_SRC_DIR)/x $(STAGE_2_TEST_SET1)
+	$(Q)$(CFG_SRC_DIR)/x test --stage 2 $(TEST_SET1)
 ci-mingw-bootstrap:
-	$(Q)$(BOOTSTRAP) $(STAGE_2_TEST_SET2)
+	$(Q)$(BOOTSTRAP) test --stage 2 $(TEST_SET2)
 ci-mingw: ci-mingw-x ci-mingw-bootstrap
 
 .PHONY: dist
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index a0be474ca3e..5e3e0ef654f 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -1051,19 +1051,22 @@ fn test_prebuilt_llvm_config_path_resolution() {
         "#,
     );
 
-    let build = Build::new(config.clone());
-    let builder = Builder::new(&build);
+    // CI-LLVM isn't always available; check if it's enabled before testing.
+    if config.llvm_from_ci {
+        let build = Build::new(config.clone());
+        let builder = Builder::new(&build);
 
-    let actual = prebuilt_llvm_config(&builder, builder.config.build, false)
-        .llvm_result()
-        .llvm_config
-        .clone();
-    let expected = builder
-        .out
-        .join(builder.config.build)
-        .join("ci-llvm/bin")
-        .join(exe("llvm-config", builder.config.build));
-    assert_eq!(expected, actual);
+        let actual = prebuilt_llvm_config(&builder, builder.config.build, false)
+            .llvm_result()
+            .llvm_config
+            .clone();
+        let expected = builder
+            .out
+            .join(builder.config.build)
+            .join("ci-llvm/bin")
+            .join(exe("llvm-config", builder.config.build));
+        assert_eq!(expected, actual);
+    }
 }
 
 #[test]
diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml
index 729cc70cb8e..cae05e1f8ae 100644
--- a/src/ci/github-actions/jobs.yml
+++ b/src/ci/github-actions/jobs.yml
@@ -456,6 +456,7 @@ auto:
   #  Windows Builders  #
   ######################
 
+  # x86_64-msvc is split into two jobs to run tests in parallel.
   - name: x86_64-msvc-1
     env:
       RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-sanitizers --enable-profiler
@@ -527,13 +528,30 @@ auto:
   # came from the mingw-w64 SourceForge download site. Unfortunately
   # SourceForge is notoriously flaky, so we mirror it on our own infrastructure.
 
-  - name: i686-mingw
+  # i686-mingw is split into three jobs to run tests in parallel.
+  - name: i686-mingw-1
     env:
       RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu
-      SCRIPT: make ci-mingw
+      SCRIPT: make ci-mingw-x-1
       # There is no dist-i686-mingw-alt, so there is no prebuilt LLVM with assertions
       NO_DOWNLOAD_CI_LLVM: 1
-    <<: *job-windows-25-8c
+    <<: *job-windows-25
+
+  - name: i686-mingw-2
+    env:
+      RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu
+      SCRIPT: make ci-mingw-x-2
+      # There is no dist-i686-mingw-alt, so there is no prebuilt LLVM with assertions
+      NO_DOWNLOAD_CI_LLVM: 1
+    <<: *job-windows-25
+
+  - name: i686-mingw-3
+    env:
+      RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu
+      SCRIPT: make ci-mingw-bootstrap
+      # There is no dist-i686-mingw-alt, so there is no prebuilt LLVM with assertions
+      NO_DOWNLOAD_CI_LLVM: 1
+    <<: *job-windows-25
 
   # x86_64-mingw is split into two jobs to run tests in parallel.
   - name: x86_64-mingw-1
diff --git a/src/ci/scripts/upload-artifacts.sh b/src/ci/scripts/upload-artifacts.sh
index 0bc91f6ba71..975b4c52726 100755
--- a/src/ci/scripts/upload-artifacts.sh
+++ b/src/ci/scripts/upload-artifacts.sh
@@ -52,10 +52,15 @@ access_url="https://ci-artifacts.rust-lang.org/${deploy_dir}/$(ciCommit)"
 # to make them easily accessible.
 if [ -n "${GITHUB_STEP_SUMMARY}" ]
 then
-  echo "# CI artifacts" >> "${GITHUB_STEP_SUMMARY}"
+  archives=($(find "${upload_dir}" -maxdepth 1 -name "*.xz"))
 
-  for filename in "${upload_dir}"/*.xz; do
-    filename=$(basename "${filename}")
-    echo "- [${filename}](${access_url}/${filename})" >> "${GITHUB_STEP_SUMMARY}"
-  done
+  # Avoid generating an invalid "*.xz" file if there are no archives
+  if [ ${#archives[@]} -gt 0 ]; then
+    echo "# CI artifacts" >> "${GITHUB_STEP_SUMMARY}"
+
+    for filename in "${upload_dir}"/*.xz; do
+      filename=$(basename "${filename}")
+      echo "- [${filename}](${access_url}/${filename})" >> "${GITHUB_STEP_SUMMARY}"
+    done
+  fi
 fi
diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md
index b6209bcb2d8..00bb2bc4dbb 100644
--- a/src/doc/rustc-dev-guide/src/tests/directives.md
+++ b/src/doc/rustc-dev-guide/src/tests/directives.md
@@ -154,6 +154,7 @@ Some examples of `X` in `ignore-X` or `only-X`:
   `ignore-coverage-map`, `ignore-coverage-run`
 - When testing a dist toolchain: `dist`
   - This needs to be enabled with `COMPILETEST_ENABLE_DIST_TESTS=1`
+- The `rustc_abi` of the target: e.g. `rustc_abi-x86_64-sse2`
 
 The following directives will check rustc build settings and target
 settings:
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index b576f28176e..bec7fbe8f52 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -13,8 +13,8 @@ use rustc_session::parse::ParseSess;
 use rustc_span::Span;
 use rustc_span::symbol::{Symbol, sym};
 
+use crate::display::Joined as _;
 use crate::html::escape::Escape;
-use crate::joined::Joined as _;
 
 #[cfg(test)]
 mod tests;
diff --git a/src/librustdoc/joined.rs b/src/librustdoc/display.rs
index f369c6cf237..ee8dde013ee 100644
--- a/src/librustdoc/joined.rs
+++ b/src/librustdoc/display.rs
@@ -1,3 +1,5 @@
+//! Various utilities for working with [`fmt::Display`] implementations.
+
 use std::fmt::{self, Display, Formatter};
 
 pub(crate) trait Joined: IntoIterator {
@@ -27,3 +29,19 @@ where
         Ok(())
     }
 }
+
+pub(crate) trait MaybeDisplay {
+    /// For a given `Option<T: Display>`, returns a `Display` implementation that will display `t` if `Some(t)`, or nothing if `None`.
+    fn maybe_display(self) -> impl Display;
+}
+
+impl<T: Display> MaybeDisplay for Option<T> {
+    fn maybe_display(self) -> impl Display {
+        fmt::from_fn(move |f| {
+            if let Some(t) = self.as_ref() {
+                t.fmt(f)?;
+            }
+            Ok(())
+        })
+    }
+}
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 086a85aa616..91b4b3ba1eb 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -30,11 +30,11 @@ use super::url_parts_builder::{UrlPartsBuilder, estimate_item_path_byte_length};
 use crate::clean::types::ExternalLocation;
 use crate::clean::utils::find_nearest_parent_module;
 use crate::clean::{self, ExternalCrate, PrimitiveType};
+use crate::display::Joined as _;
 use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
 use crate::html::escape::{Escape, EscapeBodyText};
 use crate::html::render::Context;
-use crate::joined::Joined as _;
 use crate::passes::collect_intra_doc_links::UrlFragment;
 
 pub(crate) fn write_str(s: &mut String, f: fmt::Arguments<'_>) {
@@ -709,19 +709,22 @@ fn resolved_path(
     if w.alternate() {
         write!(w, "{}{:#}", last.name, last.args.print(cx))?;
     } else {
-        let path = if use_absolute {
-            if let Ok((_, _, fqp)) = href(did, cx) {
-                format!(
-                    "{path}::{anchor}",
-                    path = join_with_double_colon(&fqp[..fqp.len() - 1]),
-                    anchor = anchor(did, *fqp.last().unwrap(), cx)
-                )
+        let path = fmt::from_fn(|f| {
+            if use_absolute {
+                if let Ok((_, _, fqp)) = href(did, cx) {
+                    write!(
+                        f,
+                        "{path}::{anchor}",
+                        path = join_with_double_colon(&fqp[..fqp.len() - 1]),
+                        anchor = anchor(did, *fqp.last().unwrap(), cx)
+                    )
+                } else {
+                    write!(f, "{}", last.name)
+                }
             } else {
-                last.name.to_string()
+                write!(f, "{}", anchor(did, last.name, cx))
             }
-        } else {
-            anchor(did, last.name, cx).to_string()
-        };
+        });
         write!(w, "{path}{args}", args = last.args.print(cx))?;
     }
     Ok(())
@@ -749,16 +752,20 @@ fn primitive_link_fragment(
         match m.primitive_locations.get(&prim) {
             Some(&def_id) if def_id.is_local() => {
                 let len = cx.current.len();
-                let path = if len == 0 {
-                    let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
-                    format!("{cname_sym}/")
-                } else {
-                    "../".repeat(len - 1)
-                };
+                let path = fmt::from_fn(|f| {
+                    if len == 0 {
+                        let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
+                        write!(f, "{cname_sym}/")?;
+                    } else {
+                        for _ in 0..(len - 1) {
+                            f.write_str("../")?;
+                        }
+                    }
+                    Ok(())
+                });
                 write!(
                     f,
-                    "<a class=\"primitive\" href=\"{}primitive.{}.html{fragment}\">",
-                    path,
+                    "<a class=\"primitive\" href=\"{path}primitive.{}.html{fragment}\">",
                     prim.as_sym()
                 )?;
                 needs_termination = true;
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index b774e60c62d..146bdd34069 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -1,8 +1,9 @@
 use std::cell::RefCell;
 use std::collections::BTreeMap;
+use std::fmt::{self, Write as _};
+use std::io;
 use std::path::{Path, PathBuf};
 use std::sync::mpsc::{Receiver, channel};
-use std::{fmt, io};
 
 use rinja::Template;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
@@ -265,12 +266,12 @@ impl<'tcx> Context<'tcx> {
                     // preventing an infinite redirection loop in the generated
                     // documentation.
 
-                    let mut path = String::new();
-                    for name in &names[..names.len() - 1] {
-                        path.push_str(name.as_str());
-                        path.push('/');
-                    }
-                    path.push_str(&item_path(ty, names.last().unwrap().as_str()));
+                    let path = fmt::from_fn(|f| {
+                        for name in &names[..names.len() - 1] {
+                            write!(f, "{name}/")?;
+                        }
+                        write!(f, "{}", item_path(ty, names.last().unwrap().as_str()))
+                    });
                     match self.shared.redirections {
                         Some(ref redirections) => {
                             let mut current_path = String::new();
@@ -278,8 +279,12 @@ impl<'tcx> Context<'tcx> {
                                 current_path.push_str(name.as_str());
                                 current_path.push('/');
                             }
-                            current_path.push_str(&item_path(ty, names.last().unwrap().as_str()));
-                            redirections.borrow_mut().insert(current_path, path);
+                            let _ = write!(
+                                current_path,
+                                "{}",
+                                item_path(ty, names.last().unwrap().as_str())
+                            );
+                            redirections.borrow_mut().insert(current_path, path.to_string());
                         }
                         None => {
                             return layout::redirect(&format!(
@@ -854,9 +859,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         if !buf.is_empty() {
             let name = item.name.as_ref().unwrap();
             let item_type = item.type_();
-            let file_name = &item_path(item_type, name.as_str());
+            let file_name = item_path(item_type, name.as_str()).to_string();
             self.shared.ensure_dir(&self.dst)?;
-            let joint_dst = self.dst.join(file_name);
+            let joint_dst = self.dst.join(&file_name);
             self.shared.fs.write(joint_dst, buf)?;
 
             if !self.info.render_redirect_pages {
@@ -873,7 +878,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
                         format!("{crate_name}/{file_name}"),
                     );
                 } else {
-                    let v = layout::redirect(file_name);
+                    let v = layout::redirect(&file_name);
                     let redir_dst = self.dst.join(redir_name);
                     self.shared.fs.write(redir_dst, v)?;
                 }
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 2f52a38c581..204631063a2 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -63,6 +63,7 @@ pub(crate) use self::context::*;
 pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources};
 pub(crate) use self::write_shared::*;
 use crate::clean::{self, ItemId, RenderedLink};
+use crate::display::{Joined as _, MaybeDisplay as _};
 use crate::error::Error;
 use crate::formats::Impl;
 use crate::formats::cache::Cache;
@@ -568,17 +569,27 @@ fn document_short<'a, 'cx: 'a>(
             let (mut summary_html, has_more_content) =
                 MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
 
-            if has_more_content {
-                let link = format!(" <a{}>Read more</a>", assoc_href_attr(item, link, cx));
+            let link = if has_more_content {
+                let link = fmt::from_fn(|f| {
+                    write!(
+                        f,
+                        " <a{}>Read more</a>",
+                        assoc_href_attr(item, link, cx).maybe_display()
+                    )
+                });
 
                 if let Some(idx) = summary_html.rfind("</p>") {
-                    summary_html.insert_str(idx, &link);
+                    summary_html.insert_str(idx, &link.to_string());
+                    None
                 } else {
-                    summary_html.push_str(&link);
+                    Some(link)
                 }
+            } else {
+                None
             }
+            .maybe_display();
 
-            write!(f, "<div class='docblock'>{summary_html}</div>")?;
+            write!(f, "<div class='docblock'>{summary_html}{link}</div>")?;
         }
         Ok(())
     })
@@ -788,13 +799,23 @@ pub(crate) fn render_impls(
 }
 
 /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
-fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
+fn assoc_href_attr<'a, 'tcx>(
+    it: &clean::Item,
+    link: AssocItemLink<'a>,
+    cx: &Context<'tcx>,
+) -> Option<impl fmt::Display + 'a + Captures<'tcx>> {
     let name = it.name.unwrap();
     let item_type = it.type_();
 
+    enum Href<'a> {
+        AnchorId(&'a str),
+        Anchor(ItemType),
+        Url(String, ItemType),
+    }
+
     let href = match link {
-        AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{id}")),
-        AssocItemLink::Anchor(None) => Some(format!("#{item_type}.{name}")),
+        AssocItemLink::Anchor(Some(ref id)) => Href::AnchorId(id),
+        AssocItemLink::Anchor(None) => Href::Anchor(item_type),
         AssocItemLink::GotoSource(did, provided_methods) => {
             // We're creating a link from the implementation of an associated item to its
             // declaration in the trait declaration.
@@ -814,7 +835,7 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>)
             };
 
             match href(did.expect_def_id(), cx) {
-                Ok((url, ..)) => Some(format!("{url}#{item_type}.{name}")),
+                Ok((url, ..)) => Href::Url(url, item_type),
                 // The link is broken since it points to an external crate that wasn't documented.
                 // Do not create any link in such case. This is better than falling back to a
                 // dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item
@@ -826,15 +847,25 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>)
                 // those two items are distinct!
                 // In this scenario, the actual `id` of this impl item would be
                 // `#{item_type}.{name}-{n}` for some number `n` (a disambiguator).
-                Err(HrefError::DocumentationNotBuilt) => None,
-                Err(_) => Some(format!("#{item_type}.{name}")),
+                Err(HrefError::DocumentationNotBuilt) => return None,
+                Err(_) => Href::Anchor(item_type),
             }
         }
     };
 
+    let href = fmt::from_fn(move |f| match &href {
+        Href::AnchorId(id) => write!(f, "#{id}"),
+        Href::Url(url, item_type) => {
+            write!(f, "{url}#{item_type}.{name}")
+        }
+        Href::Anchor(item_type) => {
+            write!(f, "#{item_type}.{name}")
+        }
+    });
+
     // If there is no `href` for the reason explained above, simply do not render it which is valid:
     // https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
-    href.map(|href| format!(" href=\"{href}\"")).unwrap_or_default()
+    Some(fmt::from_fn(move |f| write!(f, " href=\"{href}\"")))
 }
 
 #[derive(Debug)]
@@ -865,7 +896,7 @@ fn assoc_const(
             "{indent}{vis}const <a{href} class=\"constant\">{name}</a>{generics}: {ty}",
             indent = " ".repeat(indent),
             vis = visibility_print_with_space(it, cx),
-            href = assoc_href_attr(it, link, cx),
+            href = assoc_href_attr(it, link, cx).maybe_display(),
             name = it.name.as_ref().unwrap(),
             generics = generics.print(cx),
             ty = ty.print(cx),
@@ -905,7 +936,7 @@ fn assoc_type(
             "{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
             indent = " ".repeat(indent),
             vis = visibility_print_with_space(it, cx),
-            href = assoc_href_attr(it, link, cx),
+            href = assoc_href_attr(it, link, cx).maybe_display(),
             name = it.name.as_ref().unwrap(),
             generics = generics.print(cx),
         ),
@@ -948,7 +979,7 @@ fn assoc_method(
     let asyncness = header.asyncness.print_with_space();
     let safety = header.safety.print_with_space();
     let abi = print_abi_with_space(header.abi).to_string();
-    let href = assoc_href_attr(meth, link, cx);
+    let href = assoc_href_attr(meth, link, cx).maybe_display();
 
     // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
     let generics_len = format!("{:#}", g.print(cx)).len();
@@ -962,7 +993,7 @@ fn assoc_method(
         + name.as_str().len()
         + generics_len;
 
-    let notable_traits = notable_traits_button(&d.output, cx);
+    let notable_traits = notable_traits_button(&d.output, cx).maybe_display();
 
     let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
         header_len += 4;
@@ -990,7 +1021,6 @@ fn assoc_method(
             name = name,
             generics = g.print(cx),
             decl = d.full_print(header_len, indent, cx),
-            notable_traits = notable_traits.unwrap_or_default(),
             where_clause = print_where_clause(g, cx, indent, end_newline),
         ),
     );
@@ -1438,7 +1468,10 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
     }
 }
 
-pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option<String> {
+pub(crate) fn notable_traits_button<'a, 'tcx>(
+    ty: &'a clean::Type,
+    cx: &'a Context<'tcx>,
+) -> Option<impl fmt::Display + 'a + Captures<'tcx>> {
     let mut has_notable_trait = false;
 
     if ty.is_unit() {
@@ -1480,15 +1513,16 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Optio
         }
     }
 
-    if has_notable_trait {
+    has_notable_trait.then(|| {
         cx.types_with_notable_traits.borrow_mut().insert(ty.clone());
-        Some(format!(
-            " <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
-            ty = Escape(&format!("{:#}", ty.print(cx))),
-        ))
-    } else {
-        None
-    }
+        fmt::from_fn(|f| {
+            write!(
+                f,
+                " <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
+                ty = Escape(&format!("{:#}", ty.print(cx))),
+            )
+        })
+    })
 }
 
 fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
@@ -2117,11 +2151,11 @@ pub(crate) fn render_impl_summary(
 ) {
     let inner_impl = i.inner_impl();
     let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id));
-    let aliases = if aliases.is_empty() {
-        String::new()
-    } else {
-        format!(" data-aliases=\"{}\"", aliases.join(","))
-    };
+    let aliases = (!aliases.is_empty())
+        .then_some(fmt::from_fn(|f| {
+            write!(f, " data-aliases=\"{}\"", fmt::from_fn(|f| aliases.iter().joined(",", f)))
+        }))
+        .maybe_display();
     write_str(w, format_args!("<section id=\"{id}\" class=\"impl\"{aliases}>"));
     render_rightside(w, cx, &i.impl_item, RenderMode::Normal);
     write_str(
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 9c1afef75ef..d2d7415261b 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1,6 +1,6 @@
 use std::cmp::Ordering;
 use std::fmt;
-use std::fmt::{Display, Write};
+use std::fmt::Display;
 
 use itertools::Itertools;
 use rinja::Template;
@@ -27,6 +27,7 @@ use super::{
 };
 use crate::clean;
 use crate::config::ModuleSorting;
+use crate::display::{Joined as _, MaybeDisplay as _};
 use crate::formats::Impl;
 use crate::formats::item_type::ItemType;
 use crate::html::escape::{Escape, EscapeBodyTextWithWbr};
@@ -37,7 +38,6 @@ use crate::html::format::{
 use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
 use crate::html::render::{document_full, document_item_info};
 use crate::html::url_parts_builder::UrlPartsBuilder;
-use crate::joined::Joined as _;
 
 /// Generates a Rinja template struct for rendering items with common methods.
 ///
@@ -619,7 +619,7 @@ fn item_function(w: &mut String, cx: &Context<'_>, it: &clean::Item, f: &clean::
         + name.as_str().len()
         + generics_len;
 
-    let notable_traits = notable_traits_button(&f.decl.output, cx);
+    let notable_traits = notable_traits_button(&f.decl.output, cx).maybe_display();
 
     wrap_item(w, |w| {
         w.reserve(header_len);
@@ -638,7 +638,6 @@ fn item_function(w: &mut String, cx: &Context<'_>, it: &clean::Item, f: &clean::
                 generics = f.generics.print(cx),
                 where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline),
                 decl = f.decl.full_print(header_len, 0, cx),
-                notable_traits = notable_traits.unwrap_or_default(),
             ),
         );
     });
@@ -2116,34 +2115,33 @@ pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String {
     s
 }
 
-pub(super) fn item_path(ty: ItemType, name: &str) -> String {
-    match ty {
-        ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)),
-        _ => format!("{ty}.{name}.html"),
-    }
+pub(super) fn item_path(ty: ItemType, name: &str) -> impl Display + '_ {
+    fmt::from_fn(move |f| match ty {
+        ItemType::Module => write!(f, "{}index.html", ensure_trailing_slash(name)),
+        _ => write!(f, "{ty}.{name}.html"),
+    })
 }
 
-fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>) -> String {
-    let mut bounds = String::new();
-    if t_bounds.is_empty() {
-        return bounds;
-    }
-    let has_lots_of_bounds = t_bounds.len() > 2;
-    let inter_str = if has_lots_of_bounds { "\n    + " } else { " + " };
-    if !trait_alias {
-        if has_lots_of_bounds {
-            bounds.push_str(":\n    ");
-        } else {
-            bounds.push_str(": ");
-        }
-    }
-    write!(
-        bounds,
-        "{}",
-        fmt::from_fn(|f| t_bounds.iter().map(|p| p.print(cx)).joined(inter_str, f))
-    )
-    .unwrap();
-    bounds
+fn bounds<'a, 'tcx>(
+    bounds: &'a [clean::GenericBound],
+    trait_alias: bool,
+    cx: &'a Context<'tcx>,
+) -> impl Display + 'a + Captures<'tcx> {
+    (!bounds.is_empty())
+        .then_some(fmt::from_fn(move |f| {
+            let has_lots_of_bounds = bounds.len() > 2;
+            let inter_str = if has_lots_of_bounds { "\n    + " } else { " + " };
+            if !trait_alias {
+                if has_lots_of_bounds {
+                    f.write_str(":\n    ")?;
+                } else {
+                    f.write_str(": ")?;
+                }
+            }
+
+            bounds.iter().map(|p| p.print(cx)).joined(inter_str, f)
+        }))
+        .maybe_display()
 }
 
 fn wrap_item<W, F>(w: &mut W, f: F)
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index a348c6c5678..e46cc1897e9 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -2039,7 +2039,10 @@ function preLoadCss(cssUrl) {
         // Most page titles are '<Item> in <path::to::module> - Rust', except
         // modules (which don't have the first part) and keywords/primitives
         // (which don't have a module path)
-        const [item, module] = document.title.split(" in ");
+        const titleElement = document.querySelector("title");
+        const title = titleElement && titleElement.textContent ?
+                      titleElement.textContent.replace(" - Rust", "") : "";
+        const [item, module] = title.split(" in ");
         const path = [item];
         if (module !== undefined) {
             path.unshift(module);
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 121a43e3d92..ccbd6811b07 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -5318,8 +5318,9 @@ function registerSearchEvents() {
 
     // @ts-expect-error
     searchState.input.addEventListener("blur", () => {
-        // @ts-expect-error
-        searchState.input.placeholder = searchState.input.origPlaceholder;
+        if (window.searchState.input) {
+            window.searchState.input.placeholder = window.searchState.origPlaceholder;
+        }
     });
 
     // Push and pop states are used to add search results to the browser
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 4e4cff40686..e4acbcf2c62 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -106,6 +106,7 @@ macro_rules! map {
 mod clean;
 mod config;
 mod core;
+mod display;
 mod docfs;
 mod doctest;
 mod error;
@@ -114,7 +115,6 @@ mod fold;
 mod formats;
 // used by the error-index generator, so it needs to be public
 pub mod html;
-mod joined;
 mod json;
 pub(crate) mod lint;
 mod markdown;
diff --git a/src/tools/cargo b/src/tools/cargo
-Subproject 2928e32734b04925ee51e1ae88bea9a83d2fd45
+Subproject ce948f4616e3d4277e30c75c8bb01e094910df3
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index cde4f7a665c..1614c35cb1c 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -517,6 +517,7 @@ pub struct TargetCfgs {
     pub all_abis: HashSet<String>,
     pub all_families: HashSet<String>,
     pub all_pointer_widths: HashSet<String>,
+    pub all_rustc_abis: HashSet<String>,
 }
 
 impl TargetCfgs {
@@ -536,6 +537,9 @@ impl TargetCfgs {
         let mut all_abis = HashSet::new();
         let mut all_families = HashSet::new();
         let mut all_pointer_widths = HashSet::new();
+        // NOTE: for distinction between `abi` and `rustc_abi`, see comment on
+        // `TargetCfg::rustc_abi`.
+        let mut all_rustc_abis = HashSet::new();
 
         // If current target is not included in the `--print=all-target-specs-json` output,
         // we check whether it is a custom target from the user or a synthetic target from bootstrap.
@@ -576,7 +580,9 @@ impl TargetCfgs {
                 all_families.insert(family.clone());
             }
             all_pointer_widths.insert(format!("{}bit", cfg.pointer_width));
-
+            if let Some(rustc_abi) = &cfg.rustc_abi {
+                all_rustc_abis.insert(rustc_abi.clone());
+            }
             all_targets.insert(target.clone());
         }
 
@@ -590,6 +596,7 @@ impl TargetCfgs {
             all_abis,
             all_families,
             all_pointer_widths,
+            all_rustc_abis,
         }
     }
 
@@ -676,6 +683,10 @@ pub struct TargetCfg {
     pub(crate) xray: bool,
     #[serde(default = "default_reloc_model")]
     pub(crate) relocation_model: String,
+    // NOTE: `rustc_abi` should not be confused with `abi`. `rustc_abi` was introduced in #137037 to
+    // make SSE2 *required* by the ABI (kind of a hack to make a target feature *required* via the
+    // target spec).
+    pub(crate) rustc_abi: Option<String>,
 
     // Not present in target cfg json output, additional derived information.
     #[serde(skip)]
diff --git a/src/tools/compiletest/src/directive-list.rs b/src/tools/compiletest/src/directive-list.rs
index a7ac875d0a3..8c909bcb195 100644
--- a/src/tools/compiletest/src/directive-list.rs
+++ b/src/tools/compiletest/src/directive-list.rs
@@ -87,6 +87,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "ignore-remote",
     "ignore-riscv64",
     "ignore-rustc-debug-assertions",
+    "ignore-rustc_abi-x86-sse2",
     "ignore-s390x",
     "ignore-sgx",
     "ignore-sparc64",
@@ -198,6 +199,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "only-nvptx64",
     "only-powerpc",
     "only-riscv64",
+    "only-rustc_abi-x86-sse2",
     "only-s390x",
     "only-sparc",
     "only-sparc64",
diff --git a/src/tools/compiletest/src/header/cfg.rs b/src/tools/compiletest/src/header/cfg.rs
index cfe51b5655f..72a3b9d85c8 100644
--- a/src/tools/compiletest/src/header/cfg.rs
+++ b/src/tools/compiletest/src/header/cfg.rs
@@ -234,6 +234,14 @@ fn parse_cfg_name_directive<'a>(
         allowed_names: ["coverage-map", "coverage-run"],
         message: "when the test mode is {name}",
     }
+    condition! {
+        name: target_cfg.rustc_abi.as_ref().map(|abi| format!("rustc_abi-{abi}")).unwrap_or_default(),
+        allowed_names: ContainsPrefixed {
+            prefix: "rustc_abi-",
+            inner: target_cfgs.all_rustc_abis.clone(),
+        },
+        message: "when the target `rustc_abi` is {name}",
+    }
 
     condition! {
         name: "dist",
diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs
index 522d340b678..55292c46bba 100644
--- a/src/tools/compiletest/src/header/tests.rs
+++ b/src/tools/compiletest/src/header/tests.rs
@@ -889,3 +889,17 @@ fn test_needs_target_has_atomic() {
     assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8, ptr"));
     assert!(check_ignore(&config, "//@ needs-target-has-atomic: 8, ptr, 128"));
 }
+
+#[test]
+// FIXME: this test will fail against stage 0 until #137037 changes reach beta.
+#[cfg_attr(bootstrap, ignore)]
+fn test_rustc_abi() {
+    let config = cfg().target("i686-unknown-linux-gnu").build();
+    assert_eq!(config.target_cfg().rustc_abi, Some("x86-sse2".to_string()));
+    assert!(check_ignore(&config, "//@ ignore-rustc_abi-x86-sse2"));
+    assert!(!check_ignore(&config, "//@ only-rustc_abi-x86-sse2"));
+    let config = cfg().target("x86_64-unknown-linux-gnu").build();
+    assert_eq!(config.target_cfg().rustc_abi, None);
+    assert!(!check_ignore(&config, "//@ ignore-rustc_abi-x86-sse2"));
+    assert!(check_ignore(&config, "//@ only-rustc_abi-x86-sse2"));
+}
diff --git a/src/tools/generate-windows-sys/Cargo.toml b/src/tools/generate-windows-sys/Cargo.toml
index 882d3d63525..f5c0e56bb3c 100644
--- a/src/tools/generate-windows-sys/Cargo.toml
+++ b/src/tools/generate-windows-sys/Cargo.toml
@@ -4,4 +4,4 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies.windows-bindgen]
-version = "0.58.0"
+version = "0.59.0"
diff --git a/src/tools/generate-windows-sys/src/main.rs b/src/tools/generate-windows-sys/src/main.rs
index 6dbf29d957f..6bf47e84062 100644
--- a/src/tools/generate-windows-sys/src/main.rs
+++ b/src/tools/generate-windows-sys/src/main.rs
@@ -29,8 +29,7 @@ fn main() -> Result<(), Box<dyn Error>> {
 
     sort_bindings("bindings.txt")?;
 
-    let info = windows_bindgen::bindgen(["--etc", "bindings.txt"])?;
-    println!("{info}");
+    windows_bindgen::bindgen(["--etc", "bindings.txt"]);
 
     let mut f = std::fs::File::options().append(true).open("windows_sys.rs")?;
     f.write_all(ARM32_SHIM.as_bytes())?;
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index 8c4b15a65c1..82b2e35fae0 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -3,6 +3,7 @@
 #![feature(cfg_match)]
 #![feature(cell_update)]
 #![feature(float_gamma)]
+#![feature(float_erf)]
 #![feature(map_try_insert)]
 #![feature(never_type)]
 #![feature(try_blocks)]
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index 97bfb04f1f4..bedc1ebdc95 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -742,6 +742,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             | "log1pf"
             | "expm1f"
             | "tgammaf"
+            | "erff"
+            | "erfcf"
             => {
                 let [f] = this.check_shim(abi, Conv::C , link_name, args)?;
                 let f = this.read_scalar(f)?.to_f32()?;
@@ -759,6 +761,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     "log1pf" => f_host.ln_1p(),
                     "expm1f" => f_host.exp_m1(),
                     "tgammaf" => f_host.gamma(),
+                    "erff" => f_host.erf(),
+                    "erfcf" => f_host.erfc(),
                     _ => bug!(),
                 };
                 let res = res.to_soft();
@@ -799,6 +803,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             | "log1p"
             | "expm1"
             | "tgamma"
+            | "erf"
+            | "erfc"
             => {
                 let [f] = this.check_shim(abi, Conv::C , link_name, args)?;
                 let f = this.read_scalar(f)?.to_f64()?;
@@ -816,6 +822,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     "log1p" => f_host.ln_1p(),
                     "expm1" => f_host.exp_m1(),
                     "tgamma" => f_host.gamma(),
+                    "erf" => f_host.erf(),
+                    "erfc" => f_host.erfc(),
                     _ => bug!(),
                 };
                 let res = res.to_soft();
diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs
index 2f4f64b1aa8..51cafbb3f43 100644
--- a/src/tools/miri/tests/pass/float.rs
+++ b/src/tools/miri/tests/pass/float.rs
@@ -1,4 +1,5 @@
 #![feature(stmt_expr_attributes)]
+#![feature(float_erf)]
 #![feature(float_gamma)]
 #![feature(core_intrinsics)]
 #![feature(f128)]
@@ -1076,6 +1077,11 @@ pub fn libm() {
     let (val, sign) = (-0.5f64).ln_gamma();
     assert_approx_eq!(val, (2.0 * f64::consts::PI.sqrt()).ln());
     assert_eq!(sign, -1);
+
+    assert_approx_eq!(1.0f32.erf(), 0.84270079294971486934122063508260926f32);
+    assert_approx_eq!(1.0f64.erf(), 0.84270079294971486934122063508260926f64);
+    assert_approx_eq!(1.0f32.erfc(), 0.15729920705028513065877936491739074f32);
+    assert_approx_eq!(1.0f64.erfc(), 0.15729920705028513065877936491739074f64);
 }
 
 fn test_fast() {
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index d75a68d5973..e8e7dfe0d84 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -132,6 +132,7 @@ const EXCEPTIONS_CARGO: ExceptionList = &[
     ("dunce", "CC0-1.0 OR MIT-0 OR Apache-2.0"),
     ("encoding_rs", "(Apache-2.0 OR MIT) AND BSD-3-Clause"),
     ("fiat-crypto", "MIT OR Apache-2.0 OR BSD-1-Clause"),
+    ("foldhash", "Zlib"),
     ("im-rc", "MPL-2.0+"),
     ("normalize-line-endings", "Apache-2.0"),
     ("openssl", "Apache-2.0"),
diff --git a/src/version b/src/version
index b7844a6ffdc..f6342716723 100644
--- a/src/version
+++ b/src/version
@@ -1 +1 @@
-1.86.0
+1.87.0