about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorThe rustc-josh-sync Cronjob Bot <github-actions@github.com>2025-08-25 04:13:22 +0000
committerThe rustc-josh-sync Cronjob Bot <github-actions@github.com>2025-08-25 04:13:22 +0000
commit721337b92a2ea898a21ea01e420d80e2e33213d2 (patch)
treeb92935283dc39332d14862c9227eda4b865a507b /src
parentc4cd29b7fbe3a924f248ea76d5e534b4e8f9b24c (diff)
parenta1dbb443527bd126452875eb5d5860c1d001d761 (diff)
downloadrust-721337b92a2ea898a21ea01e420d80e2e33213d2.tar.gz
rust-721337b92a2ea898a21ea01e420d80e2e33213d2.zip
Merge ref 'a1dbb443527b' from rust-lang/rust
Pull recent changes from https://github.com/rust-lang/rust via Josh.

Upstream ref: a1dbb443527bd126452875eb5d5860c1d001d761
Filtered ref: c2339048a82c55166f9b9ee83fd618be252a6e23

This merge was created using https://github.com/rust-lang/josh-sync.
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/src/bin/rustc.rs10
-rw-r--r--src/bootstrap/src/core/build_steps/check.rs352
-rw-r--r--src/bootstrap/src/core/build_steps/clippy.rs33
-rw-r--r--src/bootstrap/src/core/build_steps/compile.rs190
-rw-r--r--src/bootstrap/src/core/build_steps/dist.rs112
-rw-r--r--src/bootstrap/src/core/build_steps/doc.rs8
-rw-r--r--src/bootstrap/src/core/build_steps/gcc.rs6
-rw-r--r--src/bootstrap/src/core/build_steps/llvm.rs36
-rw-r--r--src/bootstrap/src/core/build_steps/run.rs18
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs41
-rw-r--r--src/bootstrap/src/core/builder/cargo.rs37
-rw-r--r--src/bootstrap/src/core/builder/mod.rs10
-rw-r--r--src/bootstrap/src/core/builder/tests.rs97
-rw-r--r--src/bootstrap/src/core/config/config.rs1071
-rw-r--r--src/bootstrap/src/core/config/flags.rs12
-rw-r--r--src/bootstrap/src/core/config/mod.rs8
-rw-r--r--src/bootstrap/src/core/config/target_selection.rs2
-rw-r--r--src/bootstrap/src/core/download.rs70
-rw-r--r--src/bootstrap/src/core/sanity.rs18
-rw-r--r--src/bootstrap/src/lib.rs38
-rw-r--r--src/bootstrap/src/utils/change_tracker.rs10
-rw-r--r--src/bootstrap/src/utils/tracing.rs93
-rw-r--r--src/build_helper/src/util.rs11
-rw-r--r--src/ci/citool/src/analysis.rs2
-rw-r--r--src/ci/citool/src/test_dashboard.rs2
-rw-r--r--src/ci/citool/src/utils.rs2
-rw-r--r--src/ci/docker/host-x86_64/dist-various-1/Dockerfile6
-rwxr-xr-xsrc/ci/docker/host-x86_64/dist-various-1/install-emscripten.sh12
-rw-r--r--src/ci/docker/host-x86_64/dist-various-2/Dockerfile4
-rw-r--r--src/ci/docker/host-x86_64/test-various/Dockerfile7
-rw-r--r--src/ci/docker/host-x86_64/tidy/Dockerfile2
-rw-r--r--src/ci/docker/host-x86_64/tidy/eslint.version2
-rw-r--r--src/ci/github-actions/jobs.yml46
-rw-r--r--src/ci/scripts/free-disk-space-windows-start.py72
-rw-r--r--src/ci/scripts/free-disk-space-windows-wait.py77
-rw-r--r--src/ci/scripts/free-disk-space-windows.ps135
-rwxr-xr-xsrc/ci/scripts/free-disk-space.sh10
-rw-r--r--src/ci/scripts/free_disk_space_windows_util.py29
-rw-r--r--src/doc/rustc-dev-guide/src/autodiff/internals.md2
-rw-r--r--src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md8
-rw-r--r--src/doc/rustc-dev-guide/src/sanitizers.md2
-rw-r--r--src/doc/rustc-dev-guide/src/solve/candidate-preference.md2
-rw-r--r--src/doc/rustc-dev-guide/src/tests/directives.md1
-rw-r--r--src/doc/rustc-dev-guide/src/tests/ui.md1
-rw-r--r--src/doc/rustc/src/codegen-options/index.md2
-rw-r--r--src/doc/rustc/src/platform-support.md3
-rw-r--r--src/doc/rustc/src/platform-support/apple-darwin.md5
-rw-r--r--src/doc/rustc/src/platform-support/hermit.md2
-rw-r--r--src/doc/rustc/src/platform-support/openharmony.md2
-rw-r--r--src/doc/rustc/src/platform-support/wasm32-wali-linux.md2
-rw-r--r--src/doc/rustc/src/target-tier-policy.md18
-rw-r--r--src/doc/rustdoc/src/unstable-features.md4
-rw-r--r--src/doc/unstable-book/src/compiler-flags/indirect-branch-cs-prefix.md19
-rw-r--r--src/doc/unstable-book/src/compiler-flags/randomize-layout.md2
-rw-r--r--src/doc/unstable-book/src/language-features/no-sanitize.md29
-rw-r--r--src/doc/unstable-book/src/language-features/sanitize.md73
-rw-r--r--src/etc/completions/x.fish1
-rw-r--r--src/etc/completions/x.ps11
-rw-r--r--src/etc/completions/x.py.fish1
-rw-r--r--src/etc/completions/x.py.ps11
-rw-r--r--src/etc/completions/x.py.sh6
-rw-r--r--src/etc/completions/x.py.zsh1
-rw-r--r--src/etc/completions/x.sh6
-rw-r--r--src/etc/completions/x.zsh1
-rwxr-xr-xsrc/etc/htmldocck.py6
-rw-r--r--src/etc/lldb_lookup.py3
-rw-r--r--src/etc/lldb_providers.py35
-rw-r--r--src/librustdoc/Cargo.toml1
-rw-r--r--src/librustdoc/build.rs1
-rw-r--r--src/librustdoc/clean/types.rs11
-rw-r--r--src/librustdoc/config.rs11
-rw-r--r--src/librustdoc/core.rs13
-rw-r--r--src/librustdoc/formats/cache.rs8
-rw-r--r--src/librustdoc/formats/item_type.rs52
-rw-r--r--src/librustdoc/formats/renderer.rs18
-rw-r--r--src/librustdoc/html/highlight.rs295
-rw-r--r--src/librustdoc/html/layout.rs1
-rw-r--r--src/librustdoc/html/macro_expansion.rs156
-rw-r--r--src/librustdoc/html/mod.rs1
-rw-r--r--src/librustdoc/html/render/context.rs42
-rw-r--r--src/librustdoc/html/render/mod.rs202
-rw-r--r--src/librustdoc/html/render/print_item.rs125
-rw-r--r--src/librustdoc/html/render/search_index.rs2186
-rw-r--r--src/librustdoc/html/render/search_index/encode.rs244
-rw-r--r--src/librustdoc/html/render/span_map.rs2
-rw-r--r--src/librustdoc/html/render/write_shared.rs78
-rw-r--r--src/librustdoc/html/render/write_shared/tests.rs33
-rw-r--r--src/librustdoc/html/sources.rs1
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css459
-rw-r--r--src/librustdoc/html/static/js/main.js433
-rw-r--r--src/librustdoc/html/static/js/rustdoc.d.ts268
-rw-r--r--src/librustdoc/html/static/js/scrape-examples.js45
-rw-r--r--src/librustdoc/html/static/js/search.js5250
-rw-r--r--src/librustdoc/html/static/js/settings.js86
-rw-r--r--src/librustdoc/html/static/js/storage.js69
-rw-r--r--src/librustdoc/html/static/js/stringdex.d.ts165
-rw-r--r--src/librustdoc/html/static/js/stringdex.js3217
-rw-r--r--src/librustdoc/html/static/js/tsconfig.json2
-rw-r--r--src/librustdoc/html/static_files.rs1
-rw-r--r--src/librustdoc/html/templates/item_union.html3
-rw-r--r--src/librustdoc/html/templates/page.html19
-rw-r--r--src/librustdoc/html/templates/print_item.html4
-rw-r--r--src/librustdoc/json/conversions.rs2
-rw-r--r--src/librustdoc/json/mod.rs20
-rw-r--r--src/librustdoc/lib.rs50
-rw-r--r--src/librustdoc/scrape_examples.rs4
-rw-r--r--src/tools/build-manifest/Cargo.toml2
-rw-r--r--src/tools/build-manifest/src/main.rs2
-rw-r--r--src/tools/bump-stage0/Cargo.toml2
m---------src/tools/cargo0
-rw-r--r--src/tools/clippy/.github/workflows/clippy_dev.yml2
-rw-r--r--src/tools/clippy/.github/workflows/clippy_mq.yml8
-rw-r--r--src/tools/clippy/.github/workflows/clippy_pr.yml2
-rw-r--r--src/tools/clippy/.github/workflows/deploy.yml4
-rw-r--r--src/tools/clippy/.github/workflows/feature_freeze.yml2
-rw-r--r--src/tools/clippy/.github/workflows/lintcheck.yml6
-rw-r--r--src/tools/clippy/.github/workflows/remark.yml2
-rw-r--r--src/tools/clippy/CONTRIBUTING.md12
-rw-r--r--src/tools/clippy/Cargo.toml4
-rw-r--r--src/tools/clippy/book/src/continuous_integration/github_actions.md2
-rw-r--r--src/tools/clippy/book/src/development/basics.md4
-rw-r--r--src/tools/clippy/book/src/development/common_tools_writing_lints.md2
-rw-r--r--src/tools/clippy/book/src/lint_configuration.md2
-rw-r--r--src/tools/clippy/clippy_config/src/conf.rs2
-rw-r--r--src/tools/clippy/clippy_dev/src/dogfood.rs49
-rw-r--r--src/tools/clippy/clippy_dev/src/lib.rs7
-rw-r--r--src/tools/clippy/clippy_dev/src/lint.rs56
-rw-r--r--src/tools/clippy/clippy_dev/src/main.rs11
-rw-r--r--src/tools/clippy/clippy_dev/src/serve.rs107
-rw-r--r--src/tools/clippy/clippy_dev/src/setup/git_hook.rs6
-rw-r--r--src/tools/clippy/clippy_dev/src/setup/mod.rs20
-rw-r--r--src/tools/clippy/clippy_dev/src/setup/toolchain.rs19
-rw-r--r--src/tools/clippy/clippy_dev/src/setup/vscode.rs6
-rw-r--r--src/tools/clippy/clippy_dev/src/utils.rs62
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs9
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/char_lit_as_u8.rs11
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/mod.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/cognitive_complexity.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/collapsible_if.rs22
-rw-r--r--src/tools/clippy/clippy_lints/src/declared_lints.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/dereference.rs11
-rw-r--r--src/tools/clippy/clippy_lints/src/doc/mod.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/duplicate_mod.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/empty_line_after.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/eta_reduction.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/excessive_nesting.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/from_str_radix_10.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/duplicate_underscore_argument.rs34
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/mod.rs39
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs21
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/result.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/too_many_lines.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/len_zero.rs41
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs48
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_let_else.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/match_bool.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs5
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs75
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/filter_map.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/filter_next.rs18
-rw-r--r--src/tools/clippy/clippy_lints/src/misc_early/mod.rs57
-rw-r--r--src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_bool.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/no_effect.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/non_copy_const.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/non_expressive_names.rs5
-rw-r--r--src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/ptr.rs61
-rw-r--r--src/tools/clippy/clippy_lints/src/raw_strings.rs10
-rw-r--r--src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/reference.rs141
-rw-r--r--src/tools/clippy/clippy_lints/src/swap.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/mod.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs12
-rw-r--r--src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs194
-rw-r--r--src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/unwrap.rs72
-rw-r--r--src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs3
-rw-r--r--src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs15
-rw-r--r--src/tools/clippy/clippy_test_deps/Cargo.lock4
-rw-r--r--src/tools/clippy/clippy_utils/README.md2
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils/mod.rs57
-rw-r--r--src/tools/clippy/clippy_utils/src/check_proc_macro.rs9
-rw-r--r--src/tools/clippy/clippy_utils/src/diagnostics.rs15
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs17
-rw-r--r--src/tools/clippy/clippy_utils/src/msrvs.rs36
-rw-r--r--src/tools/clippy/clippy_utils/src/ty/mod.rs12
-rw-r--r--src/tools/clippy/rust-toolchain.toml2
-rw-r--r--src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr26
-rw-r--r--src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.toml12
-rw-r--r--src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/src/main.rs7
-rw-r--r--src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr35
-rw-r--r--src/tools/clippy/tests/ui/as_ptr_cast_mut.fixed38
-rw-r--r--src/tools/clippy/tests/ui/as_ptr_cast_mut.rs4
-rw-r--r--src/tools/clippy/tests/ui/as_ptr_cast_mut.stderr10
-rw-r--r--src/tools/clippy/tests/ui/as_ptr_cast_mut_unfixable.rs16
-rw-r--r--src/tools/clippy/tests/ui/as_ptr_cast_mut_unfixable.stderr11
-rw-r--r--src/tools/clippy/tests/ui/borrow_as_ptr.fixed12
-rw-r--r--src/tools/clippy/tests/ui/borrow_as_ptr.rs12
-rw-r--r--src/tools/clippy/tests/ui/borrow_as_ptr.stderr14
-rw-r--r--src/tools/clippy/tests/ui/char_lit_as_u8.fixed (renamed from src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed)0
-rw-r--r--src/tools/clippy/tests/ui/char_lit_as_u8.rs9
-rw-r--r--src/tools/clippy/tests/ui/char_lit_as_u8.stderr32
-rw-r--r--src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs12
-rw-r--r--src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr36
-rw-r--r--src/tools/clippy/tests/ui/char_lit_as_u8_unfixable.rs8
-rw-r--r--src/tools/clippy/tests/ui/char_lit_as_u8_unfixable.stderr12
-rw-r--r--src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr2
-rw-r--r--src/tools/clippy/tests/ui/deref_addrof.fixed88
-rw-r--r--src/tools/clippy/tests/ui/deref_addrof.rs88
-rw-r--r--src/tools/clippy/tests/ui/deref_addrof.stderr56
-rw-r--r--src/tools/clippy/tests/ui/doc/doc-fixable.fixed2
-rw-r--r--src/tools/clippy/tests/ui/doc/doc-fixable.rs2
-rw-r--r--src/tools/clippy/tests/ui/double_ended_iterator_last.fixed11
-rw-r--r--src/tools/clippy/tests/ui/double_ended_iterator_last.rs11
-rw-r--r--src/tools/clippy/tests/ui/double_ended_iterator_last.stderr17
-rw-r--r--src/tools/clippy/tests/ui/double_ended_iterator_last_unfixable.rs39
-rw-r--r--src/tools/clippy/tests/ui/double_ended_iterator_last_unfixable.stderr19
-rw-r--r--src/tools/clippy/tests/ui/eta.fixed29
-rw-r--r--src/tools/clippy/tests/ui/eta.rs29
-rw-r--r--src/tools/clippy/tests/ui/eta.stderr96
-rw-r--r--src/tools/clippy/tests/ui/from_str_radix_10.fixed10
-rw-r--r--src/tools/clippy/tests/ui/from_str_radix_10.rs10
-rw-r--r--src/tools/clippy/tests/ui/from_str_radix_10.stderr14
-rw-r--r--src/tools/clippy/tests/ui/functions_maxlines.rs115
-rw-r--r--src/tools/clippy/tests/ui/functions_maxlines.stderr22
-rw-r--r--src/tools/clippy/tests/ui/infinite_loops.rs71
-rw-r--r--src/tools/clippy/tests/ui/infinite_loops.stderr24
-rw-r--r--src/tools/clippy/tests/ui/match_bool.fixed13
-rw-r--r--src/tools/clippy/tests/ui/match_bool.rs13
-rw-r--r--src/tools/clippy/tests/ui/match_ref_pats.fixed36
-rw-r--r--src/tools/clippy/tests/ui/match_ref_pats.rs36
-rw-r--r--src/tools/clippy/tests/ui/match_ref_pats.stderr10
-rw-r--r--src/tools/clippy/tests/ui/ptr_arg.stderr4
-rw-r--r--src/tools/clippy/tests/ui/ptr_as_ptr.fixed8
-rw-r--r--src/tools/clippy/tests/ui/ptr_as_ptr.rs6
-rw-r--r--src/tools/clippy/tests/ui/ptr_as_ptr.stderr64
-rw-r--r--src/tools/clippy/tests/ui/similar_names.rs4
-rw-r--r--src/tools/clippy/tests/ui/similar_names.stderr4
-rw-r--r--src/tools/clippy/tests/ui/transmute.rs3
-rw-r--r--src/tools/clippy/tests/ui/transmute.stderr46
-rw-r--r--src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed3
-rw-r--r--src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs3
-rw-r--r--src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr32
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_operation.fixed10
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_operation.stderr10
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed9
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed9
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_semicolon.rs9
-rw-r--r--src/tools/clippy/tests/ui/zero_sized_hashmap_values.rs8
-rw-r--r--src/tools/clippy/tests/ui/zero_sized_hashmap_values.stderr6
-rw-r--r--src/tools/compiletest/src/common.rs7
-rw-r--r--src/tools/compiletest/src/directives.rs15
-rw-r--r--src/tools/compiletest/src/directives/directive_names.rs2
-rw-r--r--src/tools/compiletest/src/lib.rs22
-rw-r--r--src/tools/compiletest/src/runtest.rs13
-rw-r--r--src/tools/html-checker/main.rs2
-rw-r--r--src/tools/miri/.github/workflows/ci.yml18
-rw-r--r--src/tools/miri/Cargo.toml1
-rw-r--r--src/tools/miri/rust-version2
-rw-r--r--src/tools/miri/src/diagnostics.rs3
-rw-r--r--src/tools/miri/src/machine.rs13
-rw-r--r--src/tools/miri/src/operator.rs2
-rw-r--r--src/tools/miri/src/shims/extern_static.rs2
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs105
-rw-r--r--src/tools/miri/src/shims/native_lib/mod.rs10
-rw-r--r--src/tools/miri/src/shims/unix/android/foreign_items.rs11
-rwxr-xr-xsrc/tools/miri/test-cargo-miri/run-test.py2
-rw-r--r--src/tools/miri/test-cargo-miri/test.default.stdout.ref1
-rw-r--r--src/tools/miri/test-cargo-miri/test.filter.stdout.ref1
-rw-r--r--src/tools/miri/test-cargo-miri/test.multiple_targets.stdout.ref2
-rw-r--r--src/tools/miri/tests/fail/async-shared-mutable.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/alias_through_mutation.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/box_noalias_violation.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/illegal_write1.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/illegal_write5.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/load_invalid_shr.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/outdated_local.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/return_invalid_shr.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/both_borrows/zero-sized-protected.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/branchless-select-i128-pointer.rs2
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs36
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.stack.stderr25
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.tree.stderr34
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs34
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.stack.stderr25
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.tree.stderr34
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr1
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.none.stderr4
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs8
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.stack.stderr14
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.tree.stderr9
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs4
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.stack.stderr10
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.tree.stderr5
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs4
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr14
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr5
-rw-r--r--src/tools/miri/tests/fail/provenance/provenance_transmute.rs2
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/alternate-read-write.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/error-range.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/outside-range.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/protector-write-lazy.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.with.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.without.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/subtree_traversal_skipping_diagnostics.stderr1
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr1
-rw-r--r--src/tools/miri/tests/fail/validity/dangling_ref1.rs3
-rw-r--r--src/tools/miri/tests/panic/oob_subslice.stderr2
-rw-r--r--src/tools/miri/tests/panic/transmute_fat2.rs2
-rw-r--r--src/tools/miri/tests/pass/binops.rs1
-rw-r--r--src/tools/miri/tests/pass/function_calls/exported_symbol_weak.rs39
-rw-r--r--src/tools/miri/tests/pass/prefetch.rs23
-rw-r--r--src/tools/miri/tests/pass/too-large-primval-write-problem.rs2
-rw-r--r--src/tools/miri/triagebot.toml13
-rw-r--r--src/tools/nix-dev-shell/shell.nix1
-rw-r--r--src/tools/opt-dist/src/exec.rs6
-rw-r--r--src/tools/opt-dist/src/main.rs10
-rw-r--r--src/tools/rustbook/Cargo.lock69
-rw-r--r--src/tools/rustbook/Cargo.toml3
-rw-r--r--src/tools/rustdoc-js/tester.js207
-rw-r--r--src/tools/rustfmt/Cargo.toml2
-rw-r--r--src/tools/rustfmt/src/items.rs2
-rw-r--r--src/tools/rustfmt/src/modules.rs7
-rw-r--r--src/tools/rustfmt/src/visitor.rs2
-rw-r--r--src/tools/tidy/src/deps.rs33
-rw-r--r--src/tools/tidy/src/extra_checks/mod.rs47
-rw-r--r--src/tools/tidy/src/lib.rs66
-rw-r--r--src/tools/tidy/src/main.rs1
373 files changed, 13342 insertions, 7145 deletions
diff --git a/src/bootstrap/src/bin/rustc.rs b/src/bootstrap/src/bin/rustc.rs
index 5865df67b66..f15b76fa85c 100644
--- a/src/bootstrap/src/bin/rustc.rs
+++ b/src/bootstrap/src/bin/rustc.rs
@@ -179,6 +179,16 @@ fn main() {
         }
     }
 
+    // Here we pass additional paths that essentially act as a sysroot.
+    // These are used to load rustc crates (e.g. `extern crate rustc_ast;`)
+    // for rustc_private tools, so that we do not have to copy them into the
+    // actual sysroot of the compiler that builds the tool.
+    if let Ok(dirs) = env::var("RUSTC_ADDITIONAL_SYSROOT_PATHS") {
+        for dir in dirs.split(",") {
+            cmd.arg(format!("-L{dir}"));
+        }
+    }
+
     // Force all crates compiled by this compiler to (a) be unstable and (b)
     // allow the `rustc_private` feature to link to other unstable crates
     // also in the sysroot. We also do this for host crates, since those
diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs
index 4a110b733e1..bebae893ee7 100644
--- a/src/bootstrap/src/core/build_steps/check.rs
+++ b/src/bootstrap/src/core/build_steps/check.rs
@@ -1,5 +1,8 @@
 //! Implementation of compiling the compiler and standard library, in "check"-based modes.
 
+use std::fs;
+use std::path::{Path, PathBuf};
+
 use crate::core::build_steps::compile::{
     add_to_sysroot, run_cargo, rustc_cargo, rustc_cargo_env, std_cargo, std_crates_for_run_make,
 };
@@ -9,11 +12,11 @@ use crate::core::build_steps::tool::{
     prepare_tool_cargo,
 };
 use crate::core::builder::{
-    self, Alias, Builder, Kind, RunConfig, ShouldRun, Step, StepMetadata, crate_description,
+    self, Alias, Builder, Cargo, Kind, RunConfig, ShouldRun, Step, StepMetadata, crate_description,
 };
 use crate::core::config::TargetSelection;
 use crate::utils::build_stamp::{self, BuildStamp};
-use crate::{CodegenBackendKind, Compiler, Mode, Subcommand};
+use crate::{CodegenBackendKind, Compiler, Mode, Subcommand, t};
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Std {
@@ -33,7 +36,7 @@ impl Std {
 }
 
 impl Step for Std {
-    type Output = ();
+    type Output = BuildStamp;
     const DEFAULT: bool = true;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
@@ -60,13 +63,14 @@ impl Step for Std {
 
         let crates = std_crates_for_run_make(&run);
         run.builder.ensure(Std {
-            build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Std),
+            build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Std)
+                .build_compiler(),
             target: run.target,
             crates,
         });
     }
 
-    fn run(self, builder: &Builder<'_>) {
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
         let build_compiler = self.build_compiler;
         let target = self.target;
 
@@ -93,18 +97,27 @@ impl Step for Std {
             Kind::Check,
             format_args!("library artifacts{}", crate_description(&self.crates)),
             Mode::Std,
-            self.build_compiler,
+            build_compiler,
             target,
         );
 
-        let stamp = build_stamp::libstd_stamp(builder, build_compiler, target).with_prefix("check");
-        run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
+        let check_stamp =
+            build_stamp::libstd_stamp(builder, build_compiler, target).with_prefix("check");
+        run_cargo(
+            builder,
+            cargo,
+            builder.config.free_args.clone(),
+            &check_stamp,
+            vec![],
+            true,
+            false,
+        );
 
         drop(_guard);
 
         // don't check test dependencies if we haven't built libtest
         if !self.crates.iter().any(|krate| krate == "test") {
-            return;
+            return check_stamp;
         }
 
         // Then run cargo again, once we've put the rmeta files for the library
@@ -137,10 +150,11 @@ impl Step for Std {
             Kind::Check,
             "library test/bench/example targets",
             Mode::Std,
-            self.build_compiler,
+            build_compiler,
             target,
         );
         run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
+        check_stamp
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
@@ -148,12 +162,135 @@ impl Step for Std {
     }
 }
 
-/// Checks rustc using `build_compiler` and copies the built
-/// .rmeta files into the sysroot of `build_compiler`.
+/// Represents a proof that rustc was **checked**.
+/// Contains directories with .rmeta files generated by checking rustc for a specific
+/// target.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+struct RmetaSysroot {
+    host_dir: PathBuf,
+    target_dir: PathBuf,
+}
+
+impl RmetaSysroot {
+    /// Copy rmeta artifacts from the given `stamp` into a sysroot located at `directory`.
+    fn from_stamp(
+        builder: &Builder<'_>,
+        stamp: BuildStamp,
+        target: TargetSelection,
+        directory: &Path,
+    ) -> Self {
+        let host_dir = directory.join("host");
+        let target_dir = directory.join(target);
+        let _ = fs::remove_dir_all(directory);
+        t!(fs::create_dir_all(directory));
+        add_to_sysroot(builder, &target_dir, &host_dir, &stamp);
+
+        Self { host_dir, target_dir }
+    }
+
+    /// Configure the given cargo invocation so that the compiled crate will be able to use
+    /// rustc .rmeta artifacts that were previously generated.
+    fn configure_cargo(&self, cargo: &mut Cargo) {
+        cargo.append_to_env(
+            "RUSTC_ADDITIONAL_SYSROOT_PATHS",
+            format!("{},{}", self.host_dir.to_str().unwrap(), self.target_dir.to_str().unwrap()),
+            ",",
+        );
+    }
+}
+
+/// Checks rustc using the given `build_compiler` for the given `target`, and produces
+/// a sysroot in the build directory that stores the generated .rmeta files.
+///
+/// This step exists so that we can store the generated .rmeta artifacts into a separate
+/// directory, instead of copying them into the sysroot of `build_compiler`, which would
+/// "pollute" it (that is especially problematic for the external stage0 rustc).
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+struct PrepareRustcRmetaSysroot {
+    build_compiler: CompilerForCheck,
+    target: TargetSelection,
+}
+
+impl PrepareRustcRmetaSysroot {
+    fn new(build_compiler: CompilerForCheck, target: TargetSelection) -> Self {
+        Self { build_compiler, target }
+    }
+}
+
+impl Step for PrepareRustcRmetaSysroot {
+    type Output = RmetaSysroot;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.never()
+    }
+
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
+        // Check rustc
+        let stamp = builder.ensure(Rustc::from_build_compiler(
+            self.build_compiler.clone(),
+            self.target,
+            vec![],
+        ));
+
+        let build_compiler = self.build_compiler.build_compiler();
+
+        // Copy the generated rmeta artifacts to a separate directory
+        let dir = builder
+            .out
+            .join(build_compiler.host)
+            .join(format!("stage{}-rustc-rmeta-artifacts", build_compiler.stage + 1));
+        RmetaSysroot::from_stamp(builder, stamp, self.target, &dir)
+    }
+}
+
+/// Checks std using the given `build_compiler` for the given `target`, and produces
+/// a sysroot in the build directory that stores the generated .rmeta files.
+///
+/// This step exists so that we can store the generated .rmeta artifacts into a separate
+/// directory, instead of copying them into the sysroot of `build_compiler`, which would
+/// "pollute" it (that is especially problematic for the external stage0 rustc).
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+struct PrepareStdRmetaSysroot {
+    build_compiler: Compiler,
+    target: TargetSelection,
+}
+
+impl PrepareStdRmetaSysroot {
+    fn new(build_compiler: Compiler, target: TargetSelection) -> Self {
+        Self { build_compiler, target }
+    }
+}
+
+impl Step for PrepareStdRmetaSysroot {
+    type Output = RmetaSysroot;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.never()
+    }
+
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
+        // Check std
+        let stamp = builder.ensure(Std {
+            build_compiler: self.build_compiler,
+            target: self.target,
+            crates: vec![],
+        });
+
+        // Copy the generated rmeta artifacts to a separate directory
+        let dir = builder
+            .out
+            .join(self.build_compiler.host)
+            .join(format!("stage{}-std-rmeta-artifacts", self.build_compiler.stage));
+
+        RmetaSysroot::from_stamp(builder, stamp, self.target, &dir)
+    }
+}
+
+/// Checks rustc using `build_compiler`.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Rustc {
     /// Compiler that will check this rustc.
-    pub build_compiler: Compiler,
+    pub build_compiler: CompilerForCheck,
     pub target: TargetSelection,
     /// Whether to build only a subset of crates.
     ///
@@ -166,12 +303,20 @@ pub struct Rustc {
 impl Rustc {
     pub fn new(builder: &Builder<'_>, target: TargetSelection, crates: Vec<String>) -> Self {
         let build_compiler = prepare_compiler_for_check(builder, target, Mode::Rustc);
+        Self::from_build_compiler(build_compiler, target, crates)
+    }
+
+    fn from_build_compiler(
+        build_compiler: CompilerForCheck,
+        target: TargetSelection,
+        crates: Vec<String>,
+    ) -> Self {
         Self { build_compiler, target, crates }
     }
 }
 
 impl Step for Rustc {
-    type Output = ();
+    type Output = BuildStamp;
     const IS_HOST: bool = true;
     const DEFAULT: bool = true;
 
@@ -191,8 +336,8 @@ impl Step for Rustc {
     /// created will also be linked into the sysroot directory.
     ///
     /// If we check a stage 2 compiler, we will have to first build a stage 1 compiler to check it.
-    fn run(self, builder: &Builder<'_>) {
-        let build_compiler = self.build_compiler;
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
+        let build_compiler = self.build_compiler.build_compiler;
         let target = self.target;
 
         let mut cargo = builder::Cargo::new(
@@ -205,6 +350,7 @@ impl Step for Rustc {
         );
 
         rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
+        self.build_compiler.configure_cargo(&mut cargo);
 
         // Explicitly pass -p for all compiler crates -- this will force cargo
         // to also check the tests/benches/examples for these crates, rather
@@ -217,7 +363,7 @@ impl Step for Rustc {
             Kind::Check,
             format_args!("compiler artifacts{}", crate_description(&self.crates)),
             Mode::Rustc,
-            self.build_compiler,
+            self.build_compiler.build_compiler(),
             target,
         );
 
@@ -226,13 +372,12 @@ impl Step for Rustc {
 
         run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
 
-        let libdir = builder.sysroot_target_libdir(build_compiler, target);
-        let hostdir = builder.sysroot_target_libdir(build_compiler, build_compiler.host);
-        add_to_sysroot(builder, &libdir, &hostdir, &stamp);
+        stamp
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
-        let metadata = StepMetadata::check("rustc", self.target).built_by(self.build_compiler);
+        let metadata = StepMetadata::check("rustc", self.target)
+            .built_by(self.build_compiler.build_compiler());
         let metadata = if self.crates.is_empty() {
             metadata
         } else {
@@ -242,45 +387,101 @@ impl Step for Rustc {
     }
 }
 
+/// Represents a compiler that can check something.
+///
+/// If the compiler was created for `Mode::ToolRustc` or `Mode::Codegen`, it will also contain
+/// .rmeta artifacts from rustc that was already checked using `build_compiler`.
+///
+/// All steps that use this struct in a "general way" (i.e. they don't know exactly what kind of
+/// thing is being built) should call `configure_cargo` to ensure that the rmeta artifacts are
+/// properly linked, if present.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct CompilerForCheck {
+    build_compiler: Compiler,
+    rustc_rmeta_sysroot: Option<RmetaSysroot>,
+    std_rmeta_sysroot: Option<RmetaSysroot>,
+}
+
+impl CompilerForCheck {
+    pub fn build_compiler(&self) -> Compiler {
+        self.build_compiler
+    }
+
+    /// If there are any rustc rmeta artifacts available, configure the Cargo invocation
+    /// so that the artifact being built can find them.
+    pub fn configure_cargo(&self, cargo: &mut Cargo) {
+        if let Some(sysroot) = &self.rustc_rmeta_sysroot {
+            sysroot.configure_cargo(cargo);
+        }
+        if let Some(sysroot) = &self.std_rmeta_sysroot {
+            sysroot.configure_cargo(cargo);
+        }
+    }
+}
+
+/// Prepare the standard library for checking something (that requires stdlib) using
+/// `build_compiler`.
+fn prepare_std(
+    builder: &Builder<'_>,
+    build_compiler: Compiler,
+    target: TargetSelection,
+) -> Option<RmetaSysroot> {
+    // We need to build the host stdlib even if we only check, to compile build scripts and proc
+    // macros
+    builder.std(build_compiler, builder.host_target);
+
+    // If we're cross-compiling, we generate the rmeta files for the given target
+    // This check has to be here, because if we generate both .so and .rmeta files, rustc will fail,
+    // as it will have multiple candidates for linking.
+    if builder.host_target != target {
+        Some(builder.ensure(PrepareStdRmetaSysroot::new(build_compiler, target)))
+    } else {
+        None
+    }
+}
+
 /// Prepares a compiler that will check something with the given `mode`.
 pub fn prepare_compiler_for_check(
     builder: &Builder<'_>,
     target: TargetSelection,
     mode: Mode,
-) -> Compiler {
+) -> CompilerForCheck {
     let host = builder.host_target;
 
-    match mode {
+    let mut rustc_rmeta_sysroot = None;
+    let mut std_rmeta_sysroot = None;
+    let build_compiler = match mode {
         Mode::ToolBootstrap => builder.compiler(0, host),
+        // We could also only check std here and use `prepare_std`, but `ToolTarget` is currently
+        // only used for running in-tree Clippy on bootstrap tools, so it does not seem worth it to
+        // optimize it. Therefore, here we build std for the target, instead of just checking it.
         Mode::ToolTarget => get_tool_target_compiler(builder, ToolTargetBuildMode::Build(target)),
         Mode::ToolStd => {
             if builder.config.compile_time_deps {
                 // When --compile-time-deps is passed, we can't use any rustc
                 // other than the bootstrap compiler. Luckily build scripts and
                 // proc macros for tools are unlikely to need nightly.
-                return builder.compiler(0, host);
+                builder.compiler(0, host)
+            } else {
+                // These tools require the local standard library to be checked
+                let build_compiler = builder.compiler(builder.top_stage, host);
+                std_rmeta_sysroot = prepare_std(builder, build_compiler, target);
+                build_compiler
             }
-
-            // These tools require the local standard library to be checked
-            let build_compiler = builder.compiler(builder.top_stage, host);
-
-            // We need to build the host stdlib to check the tool itself.
-            // We need to build the target stdlib so that the tool can link to it.
-            builder.std(build_compiler, host);
-            // We could only check this library in theory, but `check::Std` doesn't copy rmetas
-            // into `build_compiler`'s sysroot to avoid clashes with `.rlibs`, so we build it
-            // instead.
-            builder.std(build_compiler, target);
-            build_compiler
         }
         Mode::ToolRustc | Mode::Codegen => {
             // Check Rustc to produce the required rmeta artifacts for rustc_private, and then
             // return the build compiler that was used to check rustc.
             // We do not need to check examples/tests/etc. of Rustc for rustc_private, so we pass
             // an empty set of crates, which will avoid using `cargo -p`.
-            let check = Rustc::new(builder, target, vec![]);
-            let build_compiler = check.build_compiler;
-            builder.ensure(check);
+            let compiler_for_rustc = prepare_compiler_for_check(builder, target, Mode::Rustc);
+            rustc_rmeta_sysroot = Some(
+                builder.ensure(PrepareRustcRmetaSysroot::new(compiler_for_rustc.clone(), target)),
+            );
+            let build_compiler = compiler_for_rustc.build_compiler();
+
+            // To check a rustc_private tool, we also need to check std that it will link to
+            std_rmeta_sysroot = prepare_std(builder, build_compiler, target);
             build_compiler
         }
         Mode::Rustc => {
@@ -294,15 +495,8 @@ pub fn prepare_compiler_for_check(
             let stage = if host == target { builder.top_stage - 1 } else { builder.top_stage };
             let build_compiler = builder.compiler(stage, host);
 
-            // Build host std for compiling build scripts
-            builder.std(build_compiler, build_compiler.host);
-
-            // Build target std so that the checked rustc can link to it during the check
-            // FIXME: maybe we can a way to only do a check of std here?
-            // But for that we would have to copy the stdlib rmetas to the sysroot of the build
-            // compiler, which conflicts with std rlibs, if we also build std.
-            builder.std(build_compiler, target);
-
+            // To check rustc, we need to check std that it will link to
+            std_rmeta_sysroot = prepare_std(builder, build_compiler, target);
             build_compiler
         }
         Mode::Std => {
@@ -311,13 +505,14 @@ pub fn prepare_compiler_for_check(
             // stage 0 stdlib is used to compile build scripts and proc macros.
             builder.compiler(builder.top_stage, host)
         }
-    }
+    };
+    CompilerForCheck { build_compiler, rustc_rmeta_sysroot, std_rmeta_sysroot }
 }
 
 /// Check the Cranelift codegen backend.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct CraneliftCodegenBackend {
-    build_compiler: Compiler,
+    build_compiler: CompilerForCheck,
     target: TargetSelection,
 }
 
@@ -332,12 +527,14 @@ impl Step for CraneliftCodegenBackend {
     }
 
     fn make_run(run: RunConfig<'_>) {
-        let build_compiler = prepare_compiler_for_check(run.builder, run.target, Mode::Codegen);
-        run.builder.ensure(CraneliftCodegenBackend { build_compiler, target: run.target });
+        run.builder.ensure(CraneliftCodegenBackend {
+            build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Codegen),
+            target: run.target,
+        });
     }
 
     fn run(self, builder: &Builder<'_>) {
-        let build_compiler = self.build_compiler;
+        let build_compiler = self.build_compiler.build_compiler();
         let target = self.target;
 
         let mut cargo = builder::Cargo::new(
@@ -353,12 +550,13 @@ impl Step for CraneliftCodegenBackend {
             .arg("--manifest-path")
             .arg(builder.src.join("compiler/rustc_codegen_cranelift/Cargo.toml"));
         rustc_cargo_env(builder, &mut cargo, target);
+        self.build_compiler.configure_cargo(&mut cargo);
 
         let _guard = builder.msg(
             Kind::Check,
             "rustc_codegen_cranelift",
             Mode::Codegen,
-            self.build_compiler,
+            build_compiler,
             target,
         );
 
@@ -376,7 +574,7 @@ impl Step for CraneliftCodegenBackend {
     fn metadata(&self) -> Option<StepMetadata> {
         Some(
             StepMetadata::check("rustc_codegen_cranelift", self.target)
-                .built_by(self.build_compiler),
+                .built_by(self.build_compiler.build_compiler()),
         )
     }
 }
@@ -384,7 +582,7 @@ impl Step for CraneliftCodegenBackend {
 /// Check the GCC codegen backend.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct GccCodegenBackend {
-    build_compiler: Compiler,
+    build_compiler: CompilerForCheck,
     target: TargetSelection,
 }
 
@@ -399,8 +597,10 @@ impl Step for GccCodegenBackend {
     }
 
     fn make_run(run: RunConfig<'_>) {
-        let build_compiler = prepare_compiler_for_check(run.builder, run.target, Mode::Codegen);
-        run.builder.ensure(GccCodegenBackend { build_compiler, target: run.target });
+        run.builder.ensure(GccCodegenBackend {
+            build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Codegen),
+            target: run.target,
+        });
     }
 
     fn run(self, builder: &Builder<'_>) {
@@ -410,7 +610,7 @@ impl Step for GccCodegenBackend {
             return;
         }
 
-        let build_compiler = self.build_compiler;
+        let build_compiler = self.build_compiler.build_compiler();
         let target = self.target;
 
         let mut cargo = builder::Cargo::new(
@@ -424,14 +624,10 @@ impl Step for GccCodegenBackend {
 
         cargo.arg("--manifest-path").arg(builder.src.join("compiler/rustc_codegen_gcc/Cargo.toml"));
         rustc_cargo_env(builder, &mut cargo, target);
+        self.build_compiler.configure_cargo(&mut cargo);
 
-        let _guard = builder.msg(
-            Kind::Check,
-            "rustc_codegen_gcc",
-            Mode::Codegen,
-            self.build_compiler,
-            target,
-        );
+        let _guard =
+            builder.msg(Kind::Check, "rustc_codegen_gcc", Mode::Codegen, build_compiler, target);
 
         let stamp = build_stamp::codegen_backend_stamp(
             builder,
@@ -445,7 +641,10 @@ impl Step for GccCodegenBackend {
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
-        Some(StepMetadata::check("rustc_codegen_gcc", self.target).built_by(self.build_compiler))
+        Some(
+            StepMetadata::check("rustc_codegen_gcc", self.target)
+                .built_by(self.build_compiler.build_compiler()),
+        )
     }
 }
 
@@ -467,8 +666,8 @@ macro_rules! tool_check_step {
     ) => {
         #[derive(Debug, Clone, PartialEq, Eq, Hash)]
         pub struct $name {
-            pub build_compiler: Compiler,
-            pub target: TargetSelection,
+            compiler: CompilerForCheck,
+            target: TargetSelection,
         }
 
         impl Step for $name {
@@ -486,7 +685,7 @@ macro_rules! tool_check_step {
                 let builder = run.builder;
                 let mode = $mode(builder);
 
-                let build_compiler = prepare_compiler_for_check(run.builder, target, mode);
+                let compiler = prepare_compiler_for_check(run.builder, target, mode);
 
                 // It doesn't make sense to cross-check bootstrap tools
                 if mode == Mode::ToolBootstrap && target != run.builder.host_target {
@@ -494,11 +693,11 @@ macro_rules! tool_check_step {
                     return;
                 };
 
-                run.builder.ensure($name { target, build_compiler });
+                run.builder.ensure($name { target, compiler });
             }
 
             fn run(self, builder: &Builder<'_>) {
-                let Self { target, build_compiler } = self;
+                let Self { target, compiler } = self;
                 let allow_features = {
                     let mut _value = "";
                     $( _value = $allow_features; )?
@@ -506,11 +705,11 @@ macro_rules! tool_check_step {
                 };
                 let extra_features: &[&str] = &[$($($enable_features),*)?];
                 let mode = $mode(builder);
-                run_tool_check_step(builder, build_compiler, target, $path, mode, allow_features, extra_features);
+                run_tool_check_step(builder, compiler, target, $path, mode, allow_features, extra_features);
             }
 
             fn metadata(&self) -> Option<StepMetadata> {
-                Some(StepMetadata::check(stringify!($name), self.target).built_by(self.build_compiler))
+                Some(StepMetadata::check(stringify!($name), self.target).built_by(self.compiler.build_compiler))
             }
         }
     }
@@ -519,7 +718,7 @@ macro_rules! tool_check_step {
 /// Used by the implementation of `Step::run` in `tool_check_step!`.
 fn run_tool_check_step(
     builder: &Builder<'_>,
-    build_compiler: Compiler,
+    compiler: CompilerForCheck,
     target: TargetSelection,
     path: &str,
     mode: Mode,
@@ -528,6 +727,8 @@ fn run_tool_check_step(
 ) {
     let display_name = path.rsplit('/').next().unwrap();
 
+    let build_compiler = compiler.build_compiler();
+
     let extra_features = extra_features.iter().map(|f| f.to_string()).collect::<Vec<String>>();
     let mut cargo = prepare_tool_cargo(
         builder,
@@ -544,6 +745,7 @@ fn run_tool_check_step(
         &extra_features,
     );
     cargo.allow_features(allow_features);
+    compiler.configure_cargo(&mut cargo);
 
     // FIXME: check bootstrap doesn't currently work when multiple targets are checked
     // FIXME: rust-analyzer does not work with --all-targets
diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs
index 3c4aa0886c2..05f8b240291 100644
--- a/src/bootstrap/src/core/build_steps/clippy.rs
+++ b/src/bootstrap/src/core/build_steps/clippy.rs
@@ -18,7 +18,7 @@ use build_helper::exit;
 use super::compile::{run_cargo, rustc_cargo, std_cargo};
 use super::tool::{SourceType, prepare_tool_cargo};
 use crate::builder::{Builder, ShouldRun};
-use crate::core::build_steps::check::prepare_compiler_for_check;
+use crate::core::build_steps::check::{CompilerForCheck, prepare_compiler_for_check};
 use crate::core::build_steps::compile::std_crates_for_run_make;
 use crate::core::builder;
 use crate::core::builder::{Alias, Kind, RunConfig, Step, StepMetadata, crate_description};
@@ -231,7 +231,7 @@ impl Step for Std {
 /// in-tree rustc.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Rustc {
-    build_compiler: Compiler,
+    build_compiler: CompilerForCheck,
     target: TargetSelection,
     config: LintConfig,
     /// Whether to lint only a subset of crates.
@@ -271,7 +271,7 @@ impl Step for Rustc {
     }
 
     fn run(self, builder: &Builder<'_>) {
-        let build_compiler = self.build_compiler;
+        let build_compiler = self.build_compiler.build_compiler();
         let target = self.target;
 
         let mut cargo = builder::Cargo::new(
@@ -284,6 +284,7 @@ impl Step for Rustc {
         );
 
         rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
+        self.build_compiler.configure_cargo(&mut cargo);
 
         // Explicitly pass -p for all compiler crates -- this will force cargo
         // to also lint the tests/benches/examples for these crates, rather
@@ -312,13 +313,16 @@ impl Step for Rustc {
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
-        Some(StepMetadata::clippy("rustc", self.target).built_by(self.build_compiler))
+        Some(
+            StepMetadata::clippy("rustc", self.target)
+                .built_by(self.build_compiler.build_compiler()),
+        )
     }
 }
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct CodegenGcc {
-    build_compiler: Compiler,
+    build_compiler: CompilerForCheck,
     target: TargetSelection,
     config: LintConfig,
 }
@@ -347,10 +351,10 @@ impl Step for CodegenGcc {
     }
 
     fn run(self, builder: &Builder<'_>) -> Self::Output {
-        let build_compiler = self.build_compiler;
+        let build_compiler = self.build_compiler.build_compiler();
         let target = self.target;
 
-        let cargo = prepare_tool_cargo(
+        let mut cargo = prepare_tool_cargo(
             builder,
             build_compiler,
             Mode::Codegen,
@@ -360,6 +364,7 @@ impl Step for CodegenGcc {
             SourceType::InTree,
             &[],
         );
+        self.build_compiler.configure_cargo(&mut cargo);
 
         let _guard =
             builder.msg(Kind::Clippy, "rustc_codegen_gcc", Mode::ToolRustc, build_compiler, target);
@@ -379,7 +384,10 @@ impl Step for CodegenGcc {
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
-        Some(StepMetadata::clippy("rustc_codegen_gcc", self.target).built_by(self.build_compiler))
+        Some(
+            StepMetadata::clippy("rustc_codegen_gcc", self.target)
+                .built_by(self.build_compiler.build_compiler()),
+        )
     }
 }
 
@@ -396,7 +404,7 @@ macro_rules! lint_any {
 
         #[derive(Debug, Clone, Hash, PartialEq, Eq)]
         pub struct $name {
-            build_compiler: Compiler,
+            build_compiler: CompilerForCheck,
             target: TargetSelection,
             config: LintConfig,
         }
@@ -419,9 +427,9 @@ macro_rules! lint_any {
             }
 
             fn run(self, builder: &Builder<'_>) -> Self::Output {
-                let build_compiler = self.build_compiler;
+                let build_compiler = self.build_compiler.build_compiler();
                 let target = self.target;
-                let cargo = prepare_tool_cargo(
+                let mut cargo = prepare_tool_cargo(
                     builder,
                     build_compiler,
                     $mode,
@@ -431,6 +439,7 @@ macro_rules! lint_any {
                     SourceType::InTree,
                     &[],
                 );
+                self.build_compiler.configure_cargo(&mut cargo);
 
                 let _guard = builder.msg(
                     Kind::Clippy,
@@ -456,7 +465,7 @@ macro_rules! lint_any {
             }
 
             fn metadata(&self) -> Option<StepMetadata> {
-                Some(StepMetadata::clippy($readable_name, self.target).built_by(self.build_compiler))
+                Some(StepMetadata::clippy($readable_name, self.target).built_by(self.build_compiler.build_compiler()))
             }
         }
         )+
diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index 997a152a31f..6ca32aca345 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -12,6 +12,7 @@ use std::ffi::OsStr;
 use std::io::BufReader;
 use std::io::prelude::*;
 use std::path::{Path, PathBuf};
+use std::time::SystemTime;
 use std::{env, fs, str};
 
 use serde_derive::Deserialize;
@@ -38,7 +39,7 @@ use crate::{
 };
 
 /// Build a standard library for the given `target` using the given `build_compiler`.
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Std {
     pub target: TargetSelection,
     /// Compiler that builds the standard library.
@@ -933,13 +934,22 @@ fn cp_rustc_component_to_ci_sysroot(builder: &Builder<'_>, sysroot: &Path, conte
     }
 }
 
+/// Represents information about a built rustc.
+#[derive(Clone, Debug)]
+pub struct BuiltRustc {
+    /// The compiler that actually built this *rustc*.
+    /// This can be different from the *build_compiler* passed to the `Rustc` step because of
+    /// uplifting.
+    pub build_compiler: Compiler,
+}
+
 /// Build rustc using the passed `build_compiler`.
 ///
 /// - Makes sure that `build_compiler` has a standard library prepared for its host target,
 ///   so that it can compile build scripts and proc macros when building this `rustc`.
 /// - Makes sure that `build_compiler` has a standard library prepared for `target`,
 ///   so that the built `rustc` can *link to it* and use it at runtime.
-#[derive(Debug, PartialOrd, Ord, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Rustc {
     /// The target on which rustc will run (its host).
     pub target: TargetSelection,
@@ -960,7 +970,7 @@ impl Rustc {
 }
 
 impl Step for Rustc {
-    type Output = ();
+    type Output = BuiltRustc;
 
     const IS_HOST: bool = true;
     const DEFAULT: bool = false;
@@ -1000,7 +1010,7 @@ impl Step for Rustc {
     /// This will build the compiler for a particular stage of the build using
     /// the `build_compiler` targeting the `target` architecture. The artifacts
     /// created will also be linked into the sysroot directory.
-    fn run(self, builder: &Builder<'_>) {
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
         let build_compiler = self.build_compiler;
         let target = self.target;
 
@@ -1016,7 +1026,7 @@ impl Step for Rustc {
                 &sysroot,
                 builder.config.ci_rustc_dev_contents(),
             );
-            return;
+            return BuiltRustc { build_compiler };
         }
 
         // Build a standard library for `target` using the `build_compiler`.
@@ -1028,9 +1038,9 @@ impl Step for Rustc {
 
             builder.info("WARNING: Using a potentially old librustc. This may not behave well.");
             builder.info("WARNING: Use `--keep-stage-std` if you want to rebuild the compiler when it changes");
-            builder.ensure(RustcLink::from_rustc(self, build_compiler));
+            builder.ensure(RustcLink::from_rustc(self));
 
-            return;
+            return BuiltRustc { build_compiler };
         }
 
         // The stage of the compiler that we're building
@@ -1038,25 +1048,35 @@ impl Step for Rustc {
 
         // If we are building a stage3+ compiler, and full bootstrap is disabled, and we have a
         // previous rustc available, we will uplift a compiler from a previous stage.
+        // We do not allow cross-compilation uplifting here, because there it can be quite tricky
+        // to figure out which stage actually built the rustc that should be uplifted.
         if build_compiler.stage >= 2
             && !builder.config.full_bootstrap
-            && (target == builder.host_target || builder.hosts.contains(&target))
+            && target == builder.host_target
         {
-            // If we're cross-compiling, the earliest rustc that we could have is stage 2.
-            // If we're not cross-compiling, then we should have rustc stage 1.
-            let stage_to_uplift = if target == builder.host_target { 1 } else { 2 };
-            let rustc_to_uplift = builder.compiler(stage_to_uplift, target);
-            let msg = if rustc_to_uplift.host == target {
-                format!("Uplifting rustc (stage{} -> stage{stage})", rustc_to_uplift.stage,)
-            } else {
-                format!(
-                    "Uplifting rustc (stage{}:{} -> stage{stage}:{target})",
-                    rustc_to_uplift.stage, rustc_to_uplift.host,
-                )
-            };
+            // Here we need to determine the **build compiler** that built the stage that we will
+            // be uplifting. We cannot uplift stage 1, as it has a different ABI than stage 2+,
+            // so we always uplift the stage2 compiler (compiled with stage 1).
+            let uplift_build_compiler = builder.compiler(1, build_compiler.host);
+
+            let msg = format!("Uplifting rustc from stage2 to stage{stage})");
             builder.info(&msg);
-            builder.ensure(RustcLink::from_rustc(self, rustc_to_uplift));
-            return;
+
+            // Here the compiler that built the rlibs (`uplift_build_compiler`) can be different
+            // from the compiler whose sysroot should be modified in this step. So we need to copy
+            // the (previously built) rlibs into the correct sysroot.
+            builder.ensure(RustcLink::from_build_compiler_and_sysroot(
+                // This is the compiler that actually built the rustc rlibs
+                uplift_build_compiler,
+                // We copy the rlibs into the sysroot of `build_compiler`
+                build_compiler,
+                target,
+                self.crates,
+            ));
+
+            // Here we have performed an uplift, so we return the actual build compiler that "built"
+            // this rustc.
+            return BuiltRustc { build_compiler: uplift_build_compiler };
         }
 
         // Build a standard library for the current host target using the `build_compiler`.
@@ -1129,10 +1149,8 @@ impl Step for Rustc {
             strip_debug(builder, target, &target_root_dir.join("rustc-main"));
         }
 
-        builder.ensure(RustcLink::from_rustc(
-            self,
-            builder.compiler(build_compiler.stage, builder.config.host_target),
-        ));
+        builder.ensure(RustcLink::from_rustc(self));
+        BuiltRustc { build_compiler }
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
@@ -1376,8 +1394,8 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
     if builder.config.llvm_enzyme {
         cargo.env("LLVM_ENZYME", "1");
     }
-    let llvm::LlvmResult { llvm_config, .. } = builder.ensure(llvm::Llvm { target });
-    cargo.env("LLVM_CONFIG", &llvm_config);
+    let llvm::LlvmResult { host_llvm_config, .. } = builder.ensure(llvm::Llvm { target });
+    cargo.env("LLVM_CONFIG", &host_llvm_config);
 
     // Some LLVM linker flags (-L and -l) may be needed to link `rustc_llvm`. Its build script
     // expects these to be passed via the `LLVM_LINKER_FLAGS` env variable, separated by
@@ -1441,31 +1459,51 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
     }
 }
 
-/// `RustcLink` copies all of the rlibs from the rustc build into the previous stage's sysroot.
+/// `RustcLink` copies compiler rlibs from a rustc build into a compiler sysroot.
+/// It works with (potentially up to) three compilers:
+/// - `build_compiler` is a compiler that built rustc rlibs
+/// - `sysroot_compiler` is a compiler into whose sysroot we will copy the rlibs
+///   - In most situations, `build_compiler` == `sysroot_compiler`
+/// - `target_compiler` is the compiler whose rlibs were built. It is not represented explicitly
+///   in this step, rather we just read the rlibs from a rustc build stamp of `build_compiler`.
+///
 /// This is necessary for tools using `rustc_private`, where the previous compiler will build
 /// a tool against the next compiler.
 /// To build a tool against a compiler, the rlibs of that compiler that it links against
 /// must be in the sysroot of the compiler that's doing the compiling.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 struct RustcLink {
-    /// The compiler whose rlibs we are copying around.
-    pub compiler: Compiler,
-    /// This is the compiler into whose sysroot we want to copy the rlibs into.
-    pub previous_stage_compiler: Compiler,
-    pub target: TargetSelection,
+    /// This compiler **built** some rustc, whose rlibs we will copy into a sysroot.
+    build_compiler: Compiler,
+    /// This is the compiler into whose sysroot we want to copy the built rlibs.
+    /// In most cases, it will correspond to `build_compiler`.
+    sysroot_compiler: Compiler,
+    target: TargetSelection,
     /// Not actually used; only present to make sure the cache invalidation is correct.
     crates: Vec<String>,
 }
 
 impl RustcLink {
-    fn from_rustc(rustc: Rustc, host_compiler: Compiler) -> Self {
+    /// Copy rlibs from the build compiler that build this `rustc` into the sysroot of that
+    /// build compiler.
+    fn from_rustc(rustc: Rustc) -> Self {
         Self {
-            compiler: host_compiler,
-            previous_stage_compiler: rustc.build_compiler,
+            build_compiler: rustc.build_compiler,
+            sysroot_compiler: rustc.build_compiler,
             target: rustc.target,
             crates: rustc.crates,
         }
     }
+
+    /// Copy rlibs **built** by `build_compiler` into the sysroot of `sysroot_compiler`.
+    fn from_build_compiler_and_sysroot(
+        build_compiler: Compiler,
+        sysroot_compiler: Compiler,
+        target: TargetSelection,
+        crates: Vec<String>,
+    ) -> Self {
+        Self { build_compiler, sysroot_compiler, target, crates }
+    }
 }
 
 impl Step for RustcLink {
@@ -1477,14 +1515,14 @@ impl Step for RustcLink {
 
     /// Same as `std_link`, only for librustc
     fn run(self, builder: &Builder<'_>) {
-        let compiler = self.compiler;
-        let previous_stage_compiler = self.previous_stage_compiler;
+        let build_compiler = self.build_compiler;
+        let sysroot_compiler = self.sysroot_compiler;
         let target = self.target;
         add_to_sysroot(
             builder,
-            &builder.sysroot_target_libdir(previous_stage_compiler, target),
-            &builder.sysroot_target_libdir(previous_stage_compiler, compiler.host),
-            &build_stamp::librustc_stamp(builder, compiler, target),
+            &builder.sysroot_target_libdir(sysroot_compiler, target),
+            &builder.sysroot_target_libdir(sysroot_compiler, sysroot_compiler.host),
+            &build_stamp::librustc_stamp(builder, build_compiler, target),
         );
     }
 }
@@ -1918,7 +1956,7 @@ impl Step for Sysroot {
 /// linker wrappers (LLD, LLVM bitcode linker, etc.).
 ///
 /// This will assemble a compiler in `build/$target/stage$stage`.
-#[derive(Debug, PartialOrd, Ord, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Assemble {
     /// The compiler which we will produce in this step. Assemble itself will
     /// take care of ensuring that the necessary prerequisites to do so exist,
@@ -1963,14 +2001,52 @@ impl Step for Assemble {
         if builder.config.llvm_enabled(target_compiler.host) {
             trace!("target_compiler.host" = ?target_compiler.host, "LLVM enabled");
 
-            let llvm::LlvmResult { llvm_config, .. } =
-                builder.ensure(llvm::Llvm { target: target_compiler.host });
+            let target = target_compiler.host;
+            let llvm::LlvmResult { host_llvm_config, .. } = builder.ensure(llvm::Llvm { target });
             if !builder.config.dry_run() && builder.config.llvm_tools_enabled {
                 trace!("LLVM tools enabled");
 
-                let llvm_bin_dir =
-                    command(llvm_config).arg("--bindir").run_capture_stdout(builder).stdout();
-                let llvm_bin_dir = Path::new(llvm_bin_dir.trim());
+                let host_llvm_bin_dir = command(&host_llvm_config)
+                    .arg("--bindir")
+                    .run_capture_stdout(builder)
+                    .stdout()
+                    .trim()
+                    .to_string();
+
+                let llvm_bin_dir = if target == builder.host_target {
+                    PathBuf::from(host_llvm_bin_dir)
+                } else {
+                    // If we're cross-compiling, we cannot run the target llvm-config in order to
+                    // figure out where binaries are located. We thus have to guess.
+                    let external_llvm_config = builder
+                        .config
+                        .target_config
+                        .get(&target)
+                        .and_then(|t| t.llvm_config.clone());
+                    if let Some(external_llvm_config) = external_llvm_config {
+                        // If we have an external LLVM, just hope that the bindir is the directory
+                        // where the LLVM config is located
+                        external_llvm_config.parent().unwrap().to_path_buf()
+                    } else {
+                        // If we have built LLVM locally, then take the path of the host bindir
+                        // relative to its output build directory, and then apply it to the target
+                        // LLVM output build directory.
+                        let host_llvm_out = builder.llvm_out(builder.host_target);
+                        let target_llvm_out = builder.llvm_out(target);
+                        if let Ok(relative_path) =
+                            Path::new(&host_llvm_bin_dir).strip_prefix(host_llvm_out)
+                        {
+                            target_llvm_out.join(relative_path)
+                        } else {
+                            // This is the most desperate option, just replace the host target with
+                            // the actual target in the directory path...
+                            PathBuf::from(
+                                host_llvm_bin_dir
+                                    .replace(&*builder.host_target.triple, &target.triple),
+                            )
+                        }
+                    }
+                };
 
                 // Since we've already built the LLVM tools, install them to the sysroot.
                 // This is the equivalent of installing the `llvm-tools-preview` component via
@@ -2099,7 +2175,10 @@ impl Step for Assemble {
             "target_compiler.host" = ?target_compiler.host,
             "building compiler libraries to link to"
         );
-        builder.ensure(Rustc::new(build_compiler, target_compiler.host));
+
+        // It is possible that an uplift has happened, so we override build_compiler here.
+        let BuiltRustc { build_compiler } =
+            builder.ensure(Rustc::new(build_compiler, target_compiler.host));
 
         let stage = target_compiler.stage;
         let host = target_compiler.host;
@@ -2286,6 +2365,7 @@ impl Step for Assemble {
 ///
 /// For a particular stage this will link the file listed in `stamp` into the
 /// `sysroot_dst` provided.
+#[track_caller]
 pub fn add_to_sysroot(
     builder: &Builder<'_>,
     sysroot_dst: &Path,
@@ -2568,7 +2648,17 @@ pub fn strip_debug(builder: &Builder<'_>, target: TargetSelection, path: &Path)
     }
 
     let previous_mtime = t!(t!(path.metadata()).modified());
-    command("strip").arg("--strip-debug").arg(path).run_capture(builder);
+    let stamp = BuildStamp::new(path.parent().unwrap())
+        .with_prefix(path.file_name().unwrap().to_str().unwrap())
+        .with_prefix("strip")
+        .add_stamp(previous_mtime.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos());
+
+    // Running strip can be relatively expensive (~1s on librustc_driver.so), so we don't rerun it
+    // if the file is unchanged.
+    if !stamp.is_up_to_date() {
+        command("strip").arg("--strip-debug").arg(path).run_capture(builder);
+    }
+    t!(stamp.write());
 
     let file = t!(fs::File::open(path));
 
diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs
index 414f4464d1e..daac75c03e2 100644
--- a/src/bootstrap/src/core/build_steps/dist.rs
+++ b/src/bootstrap/src/core/build_steps/dist.rs
@@ -54,7 +54,7 @@ fn should_build_extended_tool(builder: &Builder<'_>, tool: &str) -> bool {
     builder.config.tools.as_ref().is_none_or(|tools| tools.contains(tool))
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Docs {
     pub host: TargetSelection,
 }
@@ -91,7 +91,7 @@ impl Step for Docs {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct JsonDocs {
     build_compiler: Compiler,
     target: TargetSelection,
@@ -354,7 +354,7 @@ fn get_cc_search_dirs(
     (bin_path, lib_path)
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Mingw {
     pub host: TargetSelection,
 }
@@ -394,7 +394,7 @@ impl Step for Mingw {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Rustc {
     pub compiler: Compiler,
 }
@@ -730,7 +730,7 @@ fn copy_target_libs(
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Std {
     pub compiler: Compiler,
     pub target: TargetSelection,
@@ -785,7 +785,7 @@ impl Step for Std {
 /// `rust.download-rustc`.
 ///
 /// (Don't confuse this with [`RustDev`], without the `c`!)
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct RustcDev {
     pub compiler: Compiler,
     pub target: TargetSelection,
@@ -916,6 +916,12 @@ fn copy_src_dirs(
     exclude_dirs: &[&str],
     dst_dir: &Path,
 ) {
+    // The src directories should be relative to `base`, we depend on them not being absolute
+    // paths below.
+    for src_dir in src_dirs {
+        assert!(Path::new(src_dir).is_relative());
+    }
+
     // Iterating, filtering and copying a large number of directories can be quite slow.
     // Avoid doing it in dry run (and thus also tests).
     if builder.config.dry_run() {
@@ -923,6 +929,7 @@ fn copy_src_dirs(
     }
 
     fn filter_fn(exclude_dirs: &[&str], dir: &str, path: &Path) -> bool {
+        // The paths are relative, e.g. `llvm-project/...`.
         let spath = match path.to_str() {
             Some(path) => path,
             None => return false,
@@ -930,65 +937,53 @@ fn copy_src_dirs(
         if spath.ends_with('~') || spath.ends_with(".pyc") {
             return false;
         }
+        // Normalize slashes
+        let spath = spath.replace("\\", "/");
 
-        const LLVM_PROJECTS: &[&str] = &[
+        static LLVM_PROJECTS: &[&str] = &[
             "llvm-project/clang",
-            "llvm-project\\clang",
             "llvm-project/libunwind",
-            "llvm-project\\libunwind",
             "llvm-project/lld",
-            "llvm-project\\lld",
             "llvm-project/lldb",
-            "llvm-project\\lldb",
             "llvm-project/llvm",
-            "llvm-project\\llvm",
             "llvm-project/compiler-rt",
-            "llvm-project\\compiler-rt",
             "llvm-project/cmake",
-            "llvm-project\\cmake",
             "llvm-project/runtimes",
-            "llvm-project\\runtimes",
             "llvm-project/third-party",
-            "llvm-project\\third-party",
         ];
-        if spath.contains("llvm-project")
-            && !spath.ends_with("llvm-project")
-            && !LLVM_PROJECTS.iter().any(|path| spath.contains(path))
-        {
-            return false;
-        }
+        if spath.starts_with("llvm-project") && spath != "llvm-project" {
+            if !LLVM_PROJECTS.iter().any(|path| spath.starts_with(path)) {
+                return false;
+            }
 
-        // Keep only these third party libraries
-        const LLVM_THIRD_PARTY: &[&str] =
-            &["llvm-project/third-party/siphash", "llvm-project\\third-party\\siphash"];
-        if (spath.starts_with("llvm-project/third-party")
-            || spath.starts_with("llvm-project\\third-party"))
-            && !(spath.ends_with("llvm-project/third-party")
-                || spath.ends_with("llvm-project\\third-party"))
-            && !LLVM_THIRD_PARTY.iter().any(|path| spath.contains(path))
-        {
-            return false;
-        }
+            // Keep siphash third-party dependency
+            if spath.starts_with("llvm-project/third-party")
+                && spath != "llvm-project/third-party"
+                && !spath.starts_with("llvm-project/third-party/siphash")
+            {
+                return false;
+            }
 
-        const LLVM_TEST: &[&str] = &["llvm-project/llvm/test", "llvm-project\\llvm\\test"];
-        if LLVM_TEST.iter().any(|path| spath.contains(path))
-            && (spath.ends_with(".ll") || spath.ends_with(".td") || spath.ends_with(".s"))
-        {
-            return false;
+            if spath.starts_with("llvm-project/llvm/test")
+                && (spath.ends_with(".ll") || spath.ends_with(".td") || spath.ends_with(".s"))
+            {
+                return false;
+            }
         }
 
         // Cargo tests use some files like `.gitignore` that we would otherwise exclude.
-        const CARGO_TESTS: &[&str] = &["tools/cargo/tests", "tools\\cargo\\tests"];
-        if CARGO_TESTS.iter().any(|path| spath.contains(path)) {
+        if spath.starts_with("tools/cargo/tests") {
             return true;
         }
 
-        let full_path = Path::new(dir).join(path);
-        if exclude_dirs.iter().any(|excl| full_path == Path::new(excl)) {
-            return false;
+        if !exclude_dirs.is_empty() {
+            let full_path = Path::new(dir).join(path);
+            if exclude_dirs.iter().any(|excl| full_path == Path::new(excl)) {
+                return false;
+            }
         }
 
-        let excludes = [
+        static EXCLUDES: &[&str] = &[
             "CVS",
             "RCS",
             "SCCS",
@@ -1011,7 +1006,15 @@ fn copy_src_dirs(
             ".hgrags",
             "_darcs",
         ];
-        !path.iter().map(|s| s.to_str().unwrap()).any(|s| excludes.contains(&s))
+
+        // We want to check if any component of `path` doesn't contain the strings in `EXCLUDES`.
+        // However, since we traverse directories top-down in `Builder::cp_link_filtered`,
+        // it is enough to always check only the last component:
+        // - If the path is a file, we will iterate to it and then check it's filename
+        // - If the path is a dir, if it's dir name contains an excluded string, we will not even
+        //   recurse into it.
+        let last_component = path.iter().next_back().map(|s| s.to_str().unwrap()).unwrap();
+        !EXCLUDES.contains(&last_component)
     }
 
     // Copy the directories using our filter
@@ -1023,7 +1026,7 @@ fn copy_src_dirs(
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Src;
 
 impl Step for Src {
@@ -1084,7 +1087,7 @@ impl Step for Src {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct PlainSourceTarball;
 
 impl Step for PlainSourceTarball {
@@ -1230,7 +1233,7 @@ impl Step for PlainSourceTarball {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Cargo {
     pub build_compiler: Compiler,
     pub target: TargetSelection,
@@ -1284,7 +1287,7 @@ impl Step for Cargo {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct RustAnalyzer {
     pub build_compiler: Compiler,
     pub target: TargetSelection,
@@ -1560,7 +1563,7 @@ impl Step for Rustfmt {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Extended {
     stage: u32,
     host: TargetSelection,
@@ -2227,11 +2230,12 @@ fn maybe_install_llvm(
             builder.install(&llvm_dylib_path, dst_libdir, FileType::NativeLibrary);
         }
         !builder.config.dry_run()
-    } else if let llvm::LlvmBuildStatus::AlreadyBuilt(llvm::LlvmResult { llvm_config, .. }) =
-        llvm::prebuilt_llvm_config(builder, target, true)
+    } else if let llvm::LlvmBuildStatus::AlreadyBuilt(llvm::LlvmResult {
+        host_llvm_config, ..
+    }) = llvm::prebuilt_llvm_config(builder, target, true)
     {
         trace!("LLVM already built, installing LLVM files");
-        let mut cmd = command(llvm_config);
+        let mut cmd = command(host_llvm_config);
         cmd.arg("--libfiles");
         builder.verbose(|| println!("running {cmd:?}"));
         let files = cmd.run_capture_stdout(builder).stdout();
@@ -2401,7 +2405,7 @@ impl Step for LlvmTools {
 
 /// Distributes the `llvm-bitcode-linker` tool so that it can be used by a compiler whose host
 /// is `target`.
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct LlvmBitcodeLinker {
     /// The linker will be compiled by this compiler.
     pub build_compiler: Compiler,
diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs
index f6b27d83120..7fe19c00ef5 100644
--- a/src/bootstrap/src/core/build_steps/doc.rs
+++ b/src/bootstrap/src/core/build_steps/doc.rs
@@ -580,7 +580,7 @@ impl Step for SharedAssets {
 }
 
 /// Document the standard library using `build_compiler`.
-#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Std {
     build_compiler: Compiler,
     target: TargetSelection,
@@ -715,7 +715,7 @@ impl Step for Std {
 /// or remote link.
 const STD_PUBLIC_CRATES: [&str; 5] = ["core", "alloc", "std", "proc_macro", "test"];
 
-#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub enum DocumentationFormat {
     Html,
     Json,
@@ -1230,7 +1230,7 @@ fn symlink_dir_force(config: &Config, original: &Path, link: &Path) {
 }
 
 /// Builds the Rust compiler book.
-#[derive(Ord, PartialOrd, Debug, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct RustcBook {
     build_compiler: Compiler,
     target: TargetSelection,
@@ -1334,7 +1334,7 @@ impl Step for RustcBook {
 /// Documents the reference.
 /// It has to always be done using a stage 1+ compiler, because it references in-tree
 /// compiler/stdlib concepts.
-#[derive(Ord, PartialOrd, Debug, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Reference {
     build_compiler: Compiler,
     target: TargetSelection,
diff --git a/src/bootstrap/src/core/build_steps/gcc.rs b/src/bootstrap/src/core/build_steps/gcc.rs
index 2b36b0f2e27..77c9622a9bf 100644
--- a/src/bootstrap/src/core/build_steps/gcc.rs
+++ b/src/bootstrap/src/core/build_steps/gcc.rs
@@ -122,7 +122,7 @@ fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option<Pa
     match source {
         PathFreshness::LastModifiedUpstream { upstream } => {
             // Download from upstream CI
-            let root = ci_gcc_root(&builder.config);
+            let root = ci_gcc_root(&builder.config, target);
             let gcc_stamp = BuildStamp::new(&root).with_prefix("gcc").add_stamp(&upstream);
             if !gcc_stamp.is_up_to_date() && !builder.config.dry_run() {
                 builder.config.download_ci_gcc(&upstream, &root);
@@ -286,8 +286,8 @@ pub fn add_cg_gcc_cargo_flags(cargo: &mut Cargo, gcc: &GccOutput) {
 
 /// The absolute path to the downloaded GCC artifacts.
 #[cfg(not(test))]
-fn ci_gcc_root(config: &crate::Config) -> PathBuf {
-    config.out.join(config.host_target).join("ci-gcc")
+fn ci_gcc_root(config: &crate::Config, target: TargetSelection) -> PathBuf {
+    config.out.join(target).join("ci-gcc")
 }
 
 /// Detect whether GCC sources have been modified locally or not.
diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs
index 260108292e0..70259f0d1d7 100644
--- a/src/bootstrap/src/core/build_steps/llvm.rs
+++ b/src/bootstrap/src/core/build_steps/llvm.rs
@@ -29,7 +29,7 @@ use crate::{CLang, GitRepo, Kind, trace};
 pub struct LlvmResult {
     /// Path to llvm-config binary.
     /// NB: This is always the host llvm-config!
-    pub llvm_config: PathBuf,
+    pub host_llvm_config: PathBuf,
     /// Path to LLVM cmake directory for the target.
     pub llvm_cmake_dir: PathBuf,
 }
@@ -109,14 +109,14 @@ pub fn prebuilt_llvm_config(
         && let Some(ref s) = config.llvm_config
     {
         check_llvm_version(builder, s);
-        let llvm_config = s.to_path_buf();
-        let mut llvm_cmake_dir = llvm_config.clone();
+        let host_llvm_config = s.to_path_buf();
+        let mut llvm_cmake_dir = host_llvm_config.clone();
         llvm_cmake_dir.pop();
         llvm_cmake_dir.pop();
         llvm_cmake_dir.push("lib");
         llvm_cmake_dir.push("cmake");
         llvm_cmake_dir.push("llvm");
-        return LlvmBuildStatus::AlreadyBuilt(LlvmResult { llvm_config, llvm_cmake_dir });
+        return LlvmBuildStatus::AlreadyBuilt(LlvmResult { host_llvm_config, llvm_cmake_dir });
     }
 
     if handle_submodule_when_needed {
@@ -141,7 +141,7 @@ pub fn prebuilt_llvm_config(
     };
 
     let llvm_cmake_dir = out_dir.join("lib/cmake/llvm");
-    let res = LlvmResult { llvm_config: build_llvm_config, llvm_cmake_dir };
+    let res = LlvmResult { host_llvm_config: build_llvm_config, llvm_cmake_dir };
 
     static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
     let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
@@ -220,10 +220,6 @@ pub(crate) fn is_ci_llvm_available_for_target(
         ("armv7-unknown-linux-gnueabihf", false),
         ("loongarch64-unknown-linux-gnu", false),
         ("loongarch64-unknown-linux-musl", false),
-        ("mips-unknown-linux-gnu", false),
-        ("mips64-unknown-linux-gnuabi64", false),
-        ("mips64el-unknown-linux-gnuabi64", false),
-        ("mipsel-unknown-linux-gnu", false),
         ("powerpc-unknown-linux-gnu", false),
         ("powerpc64-unknown-linux-gnu", false),
         ("powerpc64le-unknown-linux-gnu", false),
@@ -487,11 +483,11 @@ impl Step for Llvm {
 
         // https://llvm.org/docs/HowToCrossCompileLLVM.html
         if !builder.config.is_host_target(target) {
-            let LlvmResult { llvm_config, .. } =
+            let LlvmResult { host_llvm_config, .. } =
                 builder.ensure(Llvm { target: builder.config.host_target });
             if !builder.config.dry_run() {
                 let llvm_bindir =
-                    command(&llvm_config).arg("--bindir").run_capture_stdout(builder).stdout();
+                    command(&host_llvm_config).arg("--bindir").run_capture_stdout(builder).stdout();
                 let host_bin = Path::new(llvm_bindir.trim());
                 cfg.define(
                     "LLVM_TABLEGEN",
@@ -500,7 +496,7 @@ impl Step for Llvm {
                 // LLVM_NM is required for cross compiling using MSVC
                 cfg.define("LLVM_NM", host_bin.join("llvm-nm").with_extension(EXE_EXTENSION));
             }
-            cfg.define("LLVM_CONFIG_PATH", llvm_config);
+            cfg.define("LLVM_CONFIG_PATH", host_llvm_config);
             if builder.config.llvm_clang {
                 let build_bin =
                     builder.llvm_out(builder.config.host_target).join("build").join("bin");
@@ -542,7 +538,7 @@ impl Step for Llvm {
 
         // Helper to find the name of LLVM's shared library on darwin and linux.
         let find_llvm_lib_name = |extension| {
-            let major = get_llvm_version_major(builder, &res.llvm_config);
+            let major = get_llvm_version_major(builder, &res.host_llvm_config);
             match &llvm_version_suffix {
                 Some(version_suffix) => format!("libLLVM-{major}{version_suffix}.{extension}"),
                 None => format!("libLLVM-{major}.{extension}"),
@@ -919,7 +915,7 @@ impl Step for Enzyme {
         }
         let target = self.target;
 
-        let LlvmResult { llvm_config, .. } = builder.ensure(Llvm { target: self.target });
+        let LlvmResult { host_llvm_config, .. } = builder.ensure(Llvm { target: self.target });
 
         static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
         let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
@@ -973,7 +969,7 @@ impl Step for Enzyme {
 
         cfg.out_dir(&out_dir)
             .profile(profile)
-            .env("LLVM_CONFIG_REAL", &llvm_config)
+            .env("LLVM_CONFIG_REAL", &host_llvm_config)
             .define("LLVM_ENABLE_ASSERTIONS", "ON")
             .define("ENZYME_EXTERNAL_SHARED_LIB", "ON")
             .define("ENZYME_BC_LOADER", "OFF")
@@ -1010,13 +1006,13 @@ impl Step for Lld {
         }
         let target = self.target;
 
-        let LlvmResult { llvm_config, llvm_cmake_dir } = builder.ensure(Llvm { target });
+        let LlvmResult { host_llvm_config, llvm_cmake_dir } = builder.ensure(Llvm { target });
 
         // The `dist` step packages LLD next to LLVM's binaries for download-ci-llvm. The root path
         // we usually expect here is `./build/$triple/ci-llvm/`, with the binaries in its `bin`
         // subfolder. We check if that's the case, and if LLD's binary already exists there next to
         // `llvm-config`: if so, we can use it instead of building LLVM/LLD from source.
-        let ci_llvm_bin = llvm_config.parent().unwrap();
+        let ci_llvm_bin = host_llvm_config.parent().unwrap();
         if ci_llvm_bin.is_dir() && ci_llvm_bin.file_name().unwrap() == "bin" {
             let lld_path = ci_llvm_bin.join(exe("lld", target));
             if lld_path.exists() {
@@ -1099,7 +1095,7 @@ impl Step for Lld {
             // Use the host llvm-tblgen binary.
             cfg.define(
                 "LLVM_TABLEGEN_EXE",
-                llvm_config.with_file_name("llvm-tblgen").with_extension(EXE_EXTENSION),
+                host_llvm_config.with_file_name("llvm-tblgen").with_extension(EXE_EXTENSION),
             );
         }
 
@@ -1140,7 +1136,7 @@ impl Step for Sanitizers {
             return runtimes;
         }
 
-        let LlvmResult { llvm_config, .. } =
+        let LlvmResult { host_llvm_config, .. } =
             builder.ensure(Llvm { target: builder.config.host_target });
 
         static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
@@ -1180,7 +1176,7 @@ impl Step for Sanitizers {
         cfg.define("COMPILER_RT_BUILD_XRAY", "OFF");
         cfg.define("COMPILER_RT_DEFAULT_TARGET_ONLY", "ON");
         cfg.define("COMPILER_RT_USE_LIBCXX", "OFF");
-        cfg.define("LLVM_CONFIG_PATH", &llvm_config);
+        cfg.define("LLVM_CONFIG_PATH", &host_llvm_config);
 
         if self.target.contains("ohos") {
             cfg.define("COMPILER_RT_USE_BUILTINS_LIBRARY", "ON");
diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs
index 7f1a7d00257..c6288f63847 100644
--- a/src/bootstrap/src/core/build_steps/run.rs
+++ b/src/bootstrap/src/core/build_steps/run.rs
@@ -17,7 +17,7 @@ use crate::core::config::flags::get_completion;
 use crate::utils::exec::command;
 use crate::{Mode, t};
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct BuildManifest;
 
 impl Step for BuildManifest {
@@ -56,7 +56,7 @@ impl Step for BuildManifest {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct BumpStage0;
 
 impl Step for BumpStage0 {
@@ -78,7 +78,7 @@ impl Step for BumpStage0 {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct ReplaceVersionPlaceholder;
 
 impl Step for ReplaceVersionPlaceholder {
@@ -169,7 +169,7 @@ impl Step for Miri {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct CollectLicenseMetadata;
 
 impl Step for CollectLicenseMetadata {
@@ -200,7 +200,7 @@ impl Step for CollectLicenseMetadata {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct GenerateCopyright;
 
 impl Step for GenerateCopyright {
@@ -264,7 +264,7 @@ impl Step for GenerateCopyright {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct GenerateWindowsSys;
 
 impl Step for GenerateWindowsSys {
@@ -326,7 +326,7 @@ impl Step for GenerateCompletions {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct UnicodeTableGenerator;
 
 impl Step for UnicodeTableGenerator {
@@ -348,7 +348,7 @@ impl Step for UnicodeTableGenerator {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct FeaturesStatusDump;
 
 impl Step for FeaturesStatusDump {
@@ -408,7 +408,7 @@ impl Step for CyclicStep {
 ///
 /// The coverage-dump tool is an internal detail of coverage tests, so this run
 /// step is only needed when testing coverage-dump manually.
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct CoverageDump;
 
 impl Step for CoverageDump {
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index 4006bed4ac5..269e7da8d7b 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -1830,10 +1830,27 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
         cmd.arg("--host").arg(&*compiler.host.triple);
         cmd.arg("--llvm-filecheck").arg(builder.llvm_filecheck(builder.config.host_target));
 
-        if let Some(codegen_backend) = builder.config.default_codegen_backend(compiler.host) {
-            // Tells compiletest which codegen backend is used by default by the compiler.
+        if let Some(codegen_backend) = builder.config.cmd.test_codegen_backend() {
+            if !builder.config.enabled_codegen_backends(compiler.host).contains(codegen_backend) {
+                eprintln!(
+                    "\
+ERROR: No configured backend named `{name}`
+HELP: You can add it into `bootstrap.toml` in `rust.codegen-backends = [{name:?}]`",
+                    name = codegen_backend.name(),
+                );
+                crate::exit!(1);
+            }
+            // Tells compiletest that we want to use this codegen in particular and to override
+            // the default one.
+            cmd.arg("--override-codegen-backend").arg(codegen_backend.name());
+            // Tells compiletest which codegen backend to use.
+            // It is used to e.g. ignore tests that don't support that codegen backend.
+            cmd.arg("--default-codegen-backend").arg(codegen_backend.name());
+        } else {
+            // Tells compiletest which codegen backend to use.
             // It is used to e.g. ignore tests that don't support that codegen backend.
-            cmd.arg("--codegen-backend").arg(codegen_backend.name());
+            cmd.arg("--default-codegen-backend")
+                .arg(builder.config.default_codegen_backend(compiler.host).unwrap().name());
         }
 
         if builder.build.config.llvm_enzyme {
@@ -2021,12 +2038,14 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
         let mut llvm_components_passed = false;
         let mut copts_passed = false;
         if builder.config.llvm_enabled(compiler.host) {
-            let llvm::LlvmResult { llvm_config, .. } =
+            let llvm::LlvmResult { host_llvm_config, .. } =
                 builder.ensure(llvm::Llvm { target: builder.config.host_target });
             if !builder.config.dry_run() {
-                let llvm_version = get_llvm_version(builder, &llvm_config);
-                let llvm_components =
-                    command(&llvm_config).arg("--components").run_capture_stdout(builder).stdout();
+                let llvm_version = get_llvm_version(builder, &host_llvm_config);
+                let llvm_components = command(&host_llvm_config)
+                    .arg("--components")
+                    .run_capture_stdout(builder)
+                    .stdout();
                 // Remove trailing newline from llvm-config output.
                 cmd.arg("--llvm-version")
                     .arg(llvm_version.trim())
@@ -2044,7 +2063,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
             // rustc args as a workaround.
             if !builder.config.dry_run() && suite.ends_with("fulldeps") {
                 let llvm_libdir =
-                    command(&llvm_config).arg("--libdir").run_capture_stdout(builder).stdout();
+                    command(&host_llvm_config).arg("--libdir").run_capture_stdout(builder).stdout();
                 let link_llvm = if target.is_msvc() {
                     format!("-Clink-arg=-LIBPATH:{llvm_libdir}")
                 } else {
@@ -2058,7 +2077,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
                 // tools. Pass the path to run-make tests so they can use them.
                 // (The coverage-run tests also need these tools to process
                 // coverage reports.)
-                let llvm_bin_path = llvm_config
+                let llvm_bin_path = host_llvm_config
                     .parent()
                     .expect("Expected llvm-config to be contained in directory");
                 assert!(llvm_bin_path.is_dir());
@@ -2732,7 +2751,7 @@ fn prepare_cargo_test(
 /// FIXME(Zalathar): Try to split this into two separate steps: a user-visible
 /// step for testing standard library crates, and an internal step used for both
 /// library crates and compiler crates.
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Crate {
     pub compiler: Compiler,
     pub target: TargetSelection,
@@ -3747,7 +3766,7 @@ impl Step for TestFloatParse {
 /// Runs the tool `src/tools/collect-license-metadata` in `ONLY_CHECK=1` mode,
 /// which verifies that `license-metadata.json` is up-to-date and therefore
 /// running the tool normally would not update anything.
-#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct CollectLicenseMetadata;
 
 impl Step for CollectLicenseMetadata {
diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs
index 3ce21eb151c..72192403412 100644
--- a/src/bootstrap/src/core/builder/cargo.rs
+++ b/src/bootstrap/src/core/builder/cargo.rs
@@ -101,6 +101,7 @@ pub struct Cargo {
 impl Cargo {
     /// Calls [`Builder::cargo`] and [`Cargo::configure_linker`] to prepare an invocation of `cargo`
     /// to be run.
+    #[track_caller]
     pub fn new(
         builder: &Builder<'_>,
         compiler: Compiler,
@@ -139,6 +140,7 @@ impl Cargo {
 
     /// Same as [`Cargo::new`] except this one doesn't configure the linker with
     /// [`Cargo::configure_linker`].
+    #[track_caller]
     pub fn new_for_mir_opt_tests(
         builder: &Builder<'_>,
         compiler: Compiler,
@@ -186,6 +188,32 @@ impl Cargo {
         self
     }
 
+    /// Append a value to an env var of the cargo command instance.
+    /// If the variable was unset previously, this is equivalent to [`Cargo::env`].
+    /// If the variable was already set, this will append `delimiter` and then `value` to it.
+    ///
+    /// Note that this only considers the existence of the env. var. configured on this `Cargo`
+    /// instance. It does not look at the environment of this process.
+    pub fn append_to_env(
+        &mut self,
+        key: impl AsRef<OsStr>,
+        value: impl AsRef<OsStr>,
+        delimiter: impl AsRef<OsStr>,
+    ) -> &mut Cargo {
+        assert_ne!(key.as_ref(), "RUSTFLAGS");
+        assert_ne!(key.as_ref(), "RUSTDOCFLAGS");
+
+        let key = key.as_ref();
+        if let Some((_, Some(previous_value))) = self.command.get_envs().find(|(k, _)| *k == key) {
+            let mut combined: OsString = previous_value.to_os_string();
+            combined.push(delimiter.as_ref());
+            combined.push(value.as_ref());
+            self.env(key, combined)
+        } else {
+            self.env(key, value)
+        }
+    }
+
     pub fn add_rustc_lib_path(&mut self, builder: &Builder<'_>) {
         builder.add_rustc_lib_path(self.compiler, &mut self.command);
     }
@@ -396,6 +424,7 @@ impl From<Cargo> for BootstrapCommand {
 
 impl Builder<'_> {
     /// Like [`Builder::cargo`], but only passes flags that are valid for all commands.
+    #[track_caller]
     pub fn bare_cargo(
         &self,
         compiler: Compiler,
@@ -480,6 +509,7 @@ impl Builder<'_> {
     /// scoped by `mode`'s output directory, it will pass the `--target` flag for the specified
     /// `target`, and will be executing the Cargo command `cmd`. `cmd` can be `miri-cmd` for
     /// commands to be run with Miri.
+    #[track_caller]
     fn cargo(
         &self,
         compiler: Compiler,
@@ -1296,7 +1326,12 @@ impl Builder<'_> {
 
             if let Some(limit) = limit
                 && (build_compiler_stage == 0
-                    || self.config.default_codegen_backend(target).unwrap_or_default().is_llvm())
+                    || self
+                        .config
+                        .default_codegen_backend(target)
+                        .cloned()
+                        .unwrap_or_default()
+                        .is_llvm())
             {
                 rustflags.arg(&format!("-Cllvm-args=-import-instr-limit={limit}"));
             }
diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs
index 043cb1c2666..40460bf168d 100644
--- a/src/bootstrap/src/core/builder/mod.rs
+++ b/src/bootstrap/src/core/builder/mod.rs
@@ -1647,11 +1647,15 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s
     ///
     /// Note that this returns `None` if LLVM is disabled, or if we're in a
     /// check build or dry-run, where there's no need to build all of LLVM.
+    ///
+    /// FIXME(@kobzol)
+    /// **WARNING**: This actually returns the **HOST** LLVM config, not LLVM config for the given
+    /// *target*.
     pub fn llvm_config(&self, target: TargetSelection) -> Option<PathBuf> {
         if self.config.llvm_enabled(target) && self.kind != Kind::Check && !self.config.dry_run() {
-            let llvm::LlvmResult { llvm_config, .. } = self.ensure(llvm::Llvm { target });
-            if llvm_config.is_file() {
-                return Some(llvm_config);
+            let llvm::LlvmResult { host_llvm_config, .. } = self.ensure(llvm::Llvm { target });
+            if host_llvm_config.is_file() {
+                return Some(host_llvm_config);
             }
         }
         None
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index a9398a654e9..2afba25ae59 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -434,14 +434,14 @@ fn test_prebuilt_llvm_config_path_resolution() {
         false,
     )
     .llvm_result()
-    .llvm_config
+    .host_llvm_config
     .clone();
     let actual = drop_win_disk_prefix_if_present(actual);
     assert_eq!(expected, actual);
 
     let actual = prebuilt_llvm_config(&builder, builder.config.host_target, false)
         .llvm_result()
-        .llvm_config
+        .host_llvm_config
         .clone();
     let actual = drop_win_disk_prefix_if_present(actual);
     assert_eq!(expected, actual);
@@ -459,7 +459,7 @@ fn test_prebuilt_llvm_config_path_resolution() {
 
     let actual = prebuilt_llvm_config(&builder, builder.config.host_target, false)
         .llvm_result()
-        .llvm_config
+        .host_llvm_config
         .clone();
     let expected = builder
         .out
@@ -482,7 +482,7 @@ fn test_prebuilt_llvm_config_path_resolution() {
 
         let actual = prebuilt_llvm_config(&builder, builder.config.host_target, false)
             .llvm_result()
-            .llvm_config
+            .host_llvm_config
             .clone();
         let expected = builder
             .out
@@ -673,6 +673,83 @@ mod snapshot {
     }
 
     #[test]
+    fn build_compiler_stage_3() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("build")
+                .path("compiler")
+                .stage(3)
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustc 1 <host> -> std 1 <host>
+        [build] rustc 1 <host> -> rustc 2 <host>
+        [build] rustc 2 <host> -> std 2 <host>
+        [build] rustc 2 <host> -> rustc 3 <host>
+        ");
+    }
+
+    #[test]
+    fn build_compiler_stage_3_cross() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("build")
+                .path("compiler")
+                .hosts(&[TEST_TRIPLE_1])
+                .stage(3)
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] llvm <target1>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustc 1 <host> -> std 1 <host>
+        [build] rustc 1 <host> -> rustc 2 <host>
+        [build] rustc 1 <host> -> std 1 <target1>
+        [build] rustc 2 <host> -> std 2 <target1>
+        [build] rustc 2 <host> -> std 2 <host>
+        [build] rustc 2 <host> -> rustc 3 <target1>
+        ");
+    }
+
+    #[test]
+    fn build_compiler_stage_3_full_bootstrap() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("build")
+                .path("compiler")
+                .stage(3)
+                .args(&["--set", "build.full-bootstrap=true"])
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustc 1 <host> -> std 1 <host>
+        [build] rustc 1 <host> -> rustc 2 <host>
+        [build] rustc 2 <host> -> std 2 <host>
+        [build] rustc 2 <host> -> rustc 3 <host>
+        ");
+    }
+
+    #[test]
+    fn build_compiler_stage_3_cross_full_bootstrap() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("build")
+                .path("compiler")
+                .stage(3)
+                .hosts(&[TEST_TRIPLE_1])
+                .args(&["--set", "build.full-bootstrap=true"])
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] llvm <target1>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustc 1 <host> -> std 1 <host>
+        [build] rustc 1 <host> -> rustc 2 <host>
+        [build] rustc 2 <host> -> std 2 <target1>
+        [build] rustc 2 <host> -> std 2 <host>
+        [build] rustc 2 <host> -> rustc 3 <target1>
+        ");
+    }
+
+    #[test]
     fn build_compiler_codegen_backend() {
         let ctx = TestCtx::new();
         insta::assert_snapshot!(
@@ -1514,7 +1591,7 @@ mod snapshot {
         insta::assert_snapshot!(
             ctx.config("check")
                 .path("compiler")
-                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (73 crates)");
+                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (74 crates)");
     }
 
     #[test]
@@ -1540,7 +1617,7 @@ mod snapshot {
             ctx.config("check")
                 .path("compiler")
                 .stage(1)
-                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (73 crates)");
+                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (74 crates)");
     }
 
     #[test]
@@ -1554,7 +1631,7 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
-        [check] rustc 1 <host> -> rustc 2 <host> (73 crates)
+        [check] rustc 1 <host> -> rustc 2 <host> (74 crates)
         ");
     }
 
@@ -1569,8 +1646,8 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
-        [build] rustc 1 <host> -> std 1 <target1>
-        [check] rustc 1 <host> -> rustc 2 <target1> (73 crates)
+        [check] rustc 1 <host> -> std 1 <target1>
+        [check] rustc 1 <host> -> rustc 2 <target1> (74 crates)
         [check] rustc 1 <host> -> rustc 2 <target1>
         [check] rustc 1 <host> -> Rustdoc 2 <target1>
         [check] rustc 1 <host> -> rustc_codegen_cranelift 2 <target1>
@@ -1666,7 +1743,7 @@ mod snapshot {
             ctx.config("check")
                 .paths(&["library", "compiler"])
                 .args(&args)
-                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (73 crates)");
+                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (74 crates)");
     }
 
     #[test]
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index 5eea5436023..84c9f9cc4d2 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -13,7 +13,6 @@
 //! and the `bootstrap.toml` file—merging them, applying defaults, and performing
 //! cross-component validation. The main `parse_inner` function and its supporting
 //! helpers reside here, transforming raw `Toml` data into the structured `Config` type.
-
 use std::cell::Cell;
 use std::collections::{BTreeSet, HashMap, HashSet};
 use std::io::IsTerminal;
@@ -48,7 +47,7 @@ use crate::core::config::toml::rust::{
 use crate::core::config::toml::target::Target;
 use crate::core::config::{
     DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt, RustcLto, SplitDebuginfo,
-    StringOrBool, set, threads_from_config,
+    StringOrBool, threads_from_config,
 };
 use crate::core::download::{
     DownloadContext, download_beta_toolchain, is_download_ci_available, maybe_download_rustfmt,
@@ -328,59 +327,6 @@ pub struct Config {
 }
 
 impl Config {
-    #[cfg_attr(
-        feature = "tracing",
-        instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::default_opts")
-    )]
-    pub fn default_opts() -> Config {
-        #[cfg(feature = "tracing")]
-        span!(target: "CONFIG_HANDLING", tracing::Level::TRACE, "constructing default config");
-
-        Config {
-            bypass_bootstrap_lock: false,
-            llvm_optimize: true,
-            ninja_in_file: true,
-            llvm_static_stdcpp: false,
-            llvm_libzstd: false,
-            backtrace: true,
-            rust_optimize: RustOptimize::Bool(true),
-            rust_optimize_tests: true,
-            rust_randomize_layout: false,
-            submodules: None,
-            docs: true,
-            docs_minification: true,
-            rust_rpath: true,
-            rust_strip: false,
-            channel: "dev".to_string(),
-            codegen_tests: true,
-            rust_dist_src: true,
-            rust_codegen_backends: vec![CodegenBackendKind::Llvm],
-            deny_warnings: true,
-            bindir: "bin".into(),
-            dist_include_mingw_linker: true,
-            dist_compression_profile: "fast".into(),
-
-            stdout_is_tty: std::io::stdout().is_terminal(),
-            stderr_is_tty: std::io::stderr().is_terminal(),
-
-            // set by build.rs
-            host_target: get_host_target(),
-
-            src: {
-                let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-                // Undo `src/bootstrap`
-                manifest_dir.parent().unwrap().parent().unwrap().to_owned()
-            },
-            out: PathBuf::from("build"),
-
-            // This is needed by codegen_ssa on macOS to ship `llvm-objcopy` aliased to
-            // `rust-objcopy` to workaround bad `strip`s on macOS.
-            llvm_tools_enabled: true,
-
-            ..Default::default()
-        }
-    }
-
     pub fn set_dry_run(&mut self, dry_run: DryRun) {
         self.exec_ctx.set_dry_run(dry_run);
     }
@@ -463,35 +409,29 @@ impl Config {
             "flags.exclude" = ?flags_exclude
         );
 
-        // First initialize the bare minimum that we need for further operation - source directory
-        // and execution context.
-        let mut config = Config::default_opts();
-        let exec_ctx = ExecutionContext::new(flags_verbose, flags_cmd.fail_fast());
-
-        config.exec_ctx = exec_ctx;
+        // Set config values based on flags.
+        let mut exec_ctx = ExecutionContext::new(flags_verbose, flags_cmd.fail_fast());
+        exec_ctx.set_dry_run(if flags_dry_run { DryRun::UserSelected } else { DryRun::Disabled });
+        let mut src = {
+            let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+            // Undo `src/bootstrap`
+            manifest_dir.parent().unwrap().parent().unwrap().to_owned()
+        };
 
-        if let Some(src) = compute_src_directory(flags_src, &config.exec_ctx) {
-            config.src = src;
+        if let Some(src_) = compute_src_directory(flags_src, &exec_ctx) {
+            src = src_;
         }
 
         // Now load the TOML config, as soon as possible
-        let (mut toml, toml_path) = load_toml_config(&config.src, flags_config, &get_toml);
-        config.config = toml_path.clone();
-
-        postprocess_toml(
-            &mut toml,
-            &config.src,
-            toml_path,
-            config.exec_ctx(),
-            &flags_set,
-            &get_toml,
-        );
+        let (mut toml, toml_path) = load_toml_config(&src, flags_config, &get_toml);
+
+        postprocess_toml(&mut toml, &src, toml_path.clone(), &exec_ctx, &flags_set, &get_toml);
 
         // Now override TOML values with flags, to make sure that we won't later override flags with
         // TOML values by accident instead, because flags have higher priority.
         let Build {
             description: build_description,
-            build: mut build_build,
+            build: build_build,
             host: build_host,
             target: build_target,
             build_dir: build_build_dir,
@@ -538,7 +478,7 @@ impl Config {
             metrics: _,
             android_ndk: build_android_ndk,
             optimized_compiler_builtins: build_optimized_compiler_builtins,
-            jobs: mut build_jobs,
+            jobs: build_jobs,
             compiletest_diff_tool: build_compiletest_diff_tool,
             compiletest_use_stage0_libtest: build_compiletest_use_stage0_libtest,
             tidy_extra_checks: build_tidy_extra_checks,
@@ -656,6 +596,58 @@ impl Config {
 
         let Gcc { download_ci_gcc: gcc_download_ci_gcc } = toml.gcc.unwrap_or_default();
 
+        if rust_optimize.as_ref().is_some_and(|v| matches!(v, RustOptimize::Bool(false))) {
+            eprintln!(
+                "WARNING: setting `optimize` to `false` is known to cause errors and \
+                should be considered unsupported. Refer to `bootstrap.example.toml` \
+                for more details."
+            );
+        }
+
+        // Prefer CLI verbosity flags if set (`flags_verbose` > 0), otherwise take the value from
+        // TOML.
+        exec_ctx.set_verbosity(cmp::max(build_verbose.unwrap_or_default() as u8, flags_verbose));
+
+        let stage0_metadata = build_helper::stage0_parser::parse_stage0_file();
+        let path_modification_cache = Arc::new(Mutex::new(HashMap::new()));
+
+        let host_target = flags_build
+            .or(build_build)
+            .map(|build| TargetSelection::from_user(&build))
+            .unwrap_or_else(get_host_target);
+        let hosts = flags_host
+            .map(|TargetSelectionList(hosts)| hosts)
+            .or_else(|| {
+                build_host.map(|h| h.iter().map(|t| TargetSelection::from_user(t)).collect())
+            })
+            .unwrap_or_else(|| vec![host_target]);
+
+        let llvm_assertions = llvm_assertions.unwrap_or(false);
+        let mut target_config = HashMap::new();
+        let mut channel = "dev".to_string();
+        let out = flags_build_dir.or(build_build_dir.map(PathBuf::from)).unwrap_or_else(|| {
+            if cfg!(test) {
+                // Use the build directory of the original x.py invocation, so that we can set `initial_rustc` properly.
+                Path::new(
+                    &env::var_os("CARGO_TARGET_DIR").expect("cargo test directly is not supported"),
+                )
+                .parent()
+                .unwrap()
+                .to_path_buf()
+            } else {
+                PathBuf::from("build")
+            }
+        });
+
+        // NOTE: Bootstrap spawns various commands with different working directories.
+        // To avoid writing to random places on the file system, `config.out` needs to be an absolute path.
+        let mut out = if !out.is_absolute() {
+            // `canonicalize` requires the path to already exist. Use our vendored copy of `absolute` instead.
+            absolute(&out).expect("can't make empty path absolute")
+        } else {
+            out
+        };
+
         if cfg!(test) {
             // When configuring bootstrap for tests, make sure to set the rustc and Cargo to the
             // same ones used to call the tests (if custom ones are not defined in the toml). If we
@@ -666,118 +658,13 @@ impl Config {
             build_cargo = build_cargo.take().or(std::env::var_os("CARGO").map(|p| p.into()));
         }
 
-        build_jobs = flags_jobs.or(build_jobs);
-        build_build = flags_build.or(build_build);
-
-        let build_dir = flags_build_dir.or(build_build_dir.map(PathBuf::from));
-        let host = if let Some(TargetSelectionList(hosts)) = flags_host {
-            Some(hosts)
-        } else {
-            build_host
-                .map(|file_host| file_host.iter().map(|h| TargetSelection::from_user(h)).collect())
-        };
-        let target = if let Some(TargetSelectionList(targets)) = flags_target {
-            Some(targets)
-        } else {
-            build_target.map(|file_target| {
-                file_target.iter().map(|h| TargetSelection::from_user(h)).collect()
-            })
-        };
-
-        if let Some(rustc) = &build_rustc
-            && !flags_skip_stage0_validation
-        {
-            check_stage0_version(rustc, "rustc", &config.src, config.exec_ctx());
-        }
-        if let Some(cargo) = &build_cargo
-            && !flags_skip_stage0_validation
-        {
-            check_stage0_version(cargo, "cargo", &config.src, config.exec_ctx());
-        }
-
-        // Prefer CLI verbosity flags if set (`flags_verbose` > 0), otherwise take the value from
-        // TOML.
-        config
-            .exec_ctx
-            .set_verbosity(cmp::max(build_verbose.unwrap_or_default() as u8, flags_verbose));
-
-        let mut paths: Vec<PathBuf> = flags_skip.into_iter().chain(flags_exclude).collect();
-        if let Some(exclude) = build_exclude {
-            paths.extend(exclude);
-        }
-
-        // Set config values based on flags.
-        config.paths = flags_paths;
-        config.include_default_paths = flags_include_default_paths;
-        config.rustc_error_format = flags_rustc_error_format;
-        config.json_output = flags_json_output;
-        config.compile_time_deps = flags_compile_time_deps;
-        config.on_fail = flags_on_fail;
-        config.cmd = flags_cmd;
-        config.incremental = flags_incremental;
-        config.set_dry_run(if flags_dry_run { DryRun::UserSelected } else { DryRun::Disabled });
-        config.dump_bootstrap_shims = flags_dump_bootstrap_shims;
-        config.keep_stage = flags_keep_stage;
-        config.keep_stage_std = flags_keep_stage_std;
-        config.color = flags_color;
-        config.free_args = flags_free_args;
-        config.llvm_profile_use = flags_llvm_profile_use;
-        config.llvm_profile_generate = flags_llvm_profile_generate;
-        config.enable_bolt_settings = flags_enable_bolt_settings;
-        config.bypass_bootstrap_lock = flags_bypass_bootstrap_lock;
-        config.is_running_on_ci = flags_ci.unwrap_or(CiEnv::is_ci());
-        config.skip_std_check_if_no_download_rustc = flags_skip_std_check_if_no_download_rustc;
-
-        // Infer the rest of the configuration.
-
-        if cfg!(test) {
-            // Use the build directory of the original x.py invocation, so that we can set `initial_rustc` properly.
-            config.out = Path::new(
-                &env::var_os("CARGO_TARGET_DIR").expect("cargo test directly is not supported"),
-            )
-            .parent()
-            .unwrap()
-            .to_path_buf();
-        }
-
-        config.compiletest_allow_stage0 = build_compiletest_allow_stage0.unwrap_or(false);
-        config.stage0_metadata = build_helper::stage0_parser::parse_stage0_file();
-
-        config.change_id = toml.change_id.inner;
-
-        config.skip = paths
-            .into_iter()
-            .map(|p| {
-                // Never return top-level path here as it would break `--skip`
-                // logic on rustc's internal test framework which is utilized
-                // by compiletest.
-                if cfg!(windows) {
-                    PathBuf::from(p.to_str().unwrap().replace('/', "\\"))
-                } else {
-                    p
-                }
-            })
-            .collect();
-
-        #[cfg(feature = "tracing")]
-        span!(
-            target: "CONFIG_HANDLING",
-            tracing::Level::TRACE,
-            "normalizing and combining `flag.skip`/`flag.exclude` paths",
-            "config.skip" = ?config.skip,
-        );
-
-        config.jobs = Some(threads_from_config(build_jobs.unwrap_or(0)));
-        if let Some(build) = build_build {
-            config.host_target = TargetSelection::from_user(&build);
-        }
-
-        set(&mut config.out, build_dir);
-        // NOTE: Bootstrap spawns various commands with different working directories.
-        // To avoid writing to random places on the file system, `config.out` needs to be an absolute path.
-        if !config.out.is_absolute() {
-            // `canonicalize` requires the path to already exist. Use our vendored copy of `absolute` instead.
-            config.out = absolute(&config.out).expect("can't make empty path absolute");
+        if !flags_skip_stage0_validation {
+            if let Some(rustc) = &build_rustc {
+                check_stage0_version(rustc, "rustc", &src, &exec_ctx);
+            }
+            if let Some(cargo) = &build_cargo {
+                check_stage0_version(cargo, "cargo", &src, &exec_ctx);
+            }
         }
 
         if build_cargo_clippy.is_some() && build_rustc.is_none() {
@@ -786,146 +673,68 @@ impl Config {
             );
         }
 
-        config.initial_rustc = if let Some(rustc) = build_rustc {
-            rustc
-        } else {
-            let dwn_ctx = DownloadContext::from(&config);
-            download_beta_toolchain(dwn_ctx);
-            config
-                .out
-                .join(config.host_target)
-                .join("stage0")
-                .join("bin")
-                .join(exe("rustc", config.host_target))
+        let is_running_on_ci = flags_ci.unwrap_or(CiEnv::is_ci());
+        let dwn_ctx = DownloadContext {
+            path_modification_cache: path_modification_cache.clone(),
+            src: &src,
+            submodules: &build_submodules,
+            host_target,
+            patch_binaries_for_nix: build_patch_binaries_for_nix,
+            exec_ctx: &exec_ctx,
+            stage0_metadata: &stage0_metadata,
+            llvm_assertions,
+            bootstrap_cache_path: &build_bootstrap_cache_path,
+            is_running_on_ci,
         };
 
-        config.initial_sysroot = t!(PathBuf::from_str(
-            command(&config.initial_rustc)
+        let initial_rustc = build_rustc.unwrap_or_else(|| {
+            download_beta_toolchain(&dwn_ctx, &out);
+            out.join(host_target).join("stage0").join("bin").join(exe("rustc", host_target))
+        });
+
+        let initial_sysroot = t!(PathBuf::from_str(
+            command(&initial_rustc)
                 .args(["--print", "sysroot"])
                 .run_in_dry_run()
-                .run_capture_stdout(&config)
+                .run_capture_stdout(&exec_ctx)
                 .stdout()
                 .trim()
         ));
 
-        config.initial_cargo_clippy = build_cargo_clippy;
-
-        config.initial_cargo = if let Some(cargo) = build_cargo {
-            cargo
-        } else {
-            let dwn_ctx = DownloadContext::from(&config);
-            download_beta_toolchain(dwn_ctx);
-            config.initial_sysroot.join("bin").join(exe("cargo", config.host_target))
-        };
+        let initial_cargo = build_cargo.unwrap_or_else(|| {
+            download_beta_toolchain(&dwn_ctx, &out);
+            initial_sysroot.join("bin").join(exe("cargo", host_target))
+        });
 
         // NOTE: it's important this comes *after* we set `initial_rustc` just above.
-        if config.dry_run() {
-            let dir = config.out.join("tmp-dry-run");
-            t!(fs::create_dir_all(&dir));
-            config.out = dir;
+        if exec_ctx.dry_run() {
+            out = out.join("tmp-dry-run");
+            fs::create_dir_all(&out).expect("Failed to create dry-run directory");
         }
 
-        config.hosts = if let Some(hosts) = host { hosts } else { vec![config.host_target] };
-        config.targets = if let Some(targets) = target {
-            targets
-        } else {
-            // If target is *not* configured, then default to the host
-            // toolchains.
-            config.hosts.clone()
-        };
-
-        config.nodejs = build_nodejs.map(PathBuf::from);
-        config.npm = build_npm.map(PathBuf::from);
-        config.gdb = build_gdb.map(PathBuf::from);
-        config.lldb = build_lldb.map(PathBuf::from);
-        config.python = build_python.map(PathBuf::from);
-        config.reuse = build_reuse.map(PathBuf::from);
-        config.submodules = build_submodules;
-        config.android_ndk = build_android_ndk;
-        config.bootstrap_cache_path = build_bootstrap_cache_path;
-        set(&mut config.low_priority, build_low_priority);
-        set(&mut config.compiler_docs, build_compiler_docs);
-        set(&mut config.library_docs_private_items, build_library_docs_private_items);
-        set(&mut config.docs_minification, build_docs_minification);
-        set(&mut config.docs, build_docs);
-        set(&mut config.locked_deps, build_locked_deps);
-        set(&mut config.full_bootstrap, build_full_bootstrap);
-        set(&mut config.extended, build_extended);
-        config.tools = build_tools;
-        set(&mut config.tool, build_tool);
-        set(&mut config.sanitizers, build_sanitizers);
-        set(&mut config.profiler, build_profiler);
-        set(&mut config.cargo_native_static, build_cargo_native_static);
-        set(&mut config.configure_args, build_configure_args);
-        set(&mut config.local_rebuild, build_local_rebuild);
-        set(&mut config.print_step_timings, build_print_step_timings);
-        set(&mut config.print_step_rusage, build_print_step_rusage);
-        config.patch_binaries_for_nix = build_patch_binaries_for_nix;
-
-        // Verbose flag is a good default for `rust.verbose-tests`.
-        config.verbose_tests = config.is_verbose();
-
-        config.prefix = install_prefix.map(PathBuf::from);
-        config.sysconfdir = install_sysconfdir.map(PathBuf::from);
-        config.datadir = install_datadir.map(PathBuf::from);
-        config.docdir = install_docdir.map(PathBuf::from);
-        set(&mut config.bindir, install_bindir.map(PathBuf::from));
-        config.libdir = install_libdir.map(PathBuf::from);
-        config.mandir = install_mandir.map(PathBuf::from);
-
-        config.llvm_assertions = llvm_assertions.unwrap_or(false);
-
-        let file_content = t!(fs::read_to_string(config.src.join("src/ci/channel")));
+        let file_content = t!(fs::read_to_string(src.join("src/ci/channel")));
         let ci_channel = file_content.trim_end();
 
         let is_user_configured_rust_channel = match rust_channel {
-            Some(channel) if channel == "auto-detect" => {
-                config.channel = ci_channel.into();
+            Some(channel_) if channel_ == "auto-detect" => {
+                channel = ci_channel.into();
                 true
             }
-            Some(channel) => {
-                config.channel = channel;
+            Some(channel_) => {
+                channel = channel_;
                 true
             }
             None => false,
         };
 
-        let default = config.channel == "dev";
-        config.omit_git_hash = rust_omit_git_hash.unwrap_or(default);
+        let omit_git_hash = rust_omit_git_hash.unwrap_or(channel == "dev");
 
-        config.rust_info = git_info(&config.exec_ctx, config.omit_git_hash, &config.src);
-        config.cargo_info =
-            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/cargo"));
-        config.rust_analyzer_info = git_info(
-            &config.exec_ctx,
-            config.omit_git_hash,
-            &config.src.join("src/tools/rust-analyzer"),
-        );
-        config.clippy_info =
-            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/clippy"));
-        config.miri_info =
-            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/miri"));
-        config.rustfmt_info =
-            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
-        config.enzyme_info =
-            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/enzyme"));
-        config.in_tree_llvm_info =
-            git_info(&config.exec_ctx, false, &config.src.join("src/llvm-project"));
-        config.in_tree_gcc_info = git_info(&config.exec_ctx, false, &config.src.join("src/gcc"));
-
-        config.vendor = build_vendor.unwrap_or(
-            config.rust_info.is_from_tarball()
-                && config.src.join("vendor").exists()
-                && config.src.join(".cargo/config.toml").exists(),
-        );
+        let rust_info = git_info(&exec_ctx, omit_git_hash, &src);
 
-        if !is_user_configured_rust_channel && config.rust_info.is_from_tarball() {
-            config.channel = ci_channel.into();
+        if !is_user_configured_rust_channel && rust_info.is_from_tarball() {
+            channel = ci_channel.into();
         }
 
-        config.rust_profile_use = flags_rust_profile_use;
-        config.rust_profile_generate = flags_rust_profile_generate;
-
         // FIXME(#133381): alt rustc builds currently do *not* have rustc debug assertions
         // enabled. We should not download a CI alt rustc if we need rustc to have debug
         // assertions (e.g. for crashes test suite). This can be changed once something like
@@ -951,17 +760,32 @@ impl Config {
             );
         }
 
-        let dwn_ctx = DownloadContext::from(&config);
-        config.download_rustc_commit =
-            download_ci_rustc_commit(dwn_ctx, rust_download_rustc, config.llvm_assertions);
+        let mut download_rustc_commit =
+            download_ci_rustc_commit(&dwn_ctx, &rust_info, rust_download_rustc, llvm_assertions);
 
-        if debug_assertions_requested && config.download_rustc_commit.is_some() {
+        if debug_assertions_requested && download_rustc_commit.is_some() {
             eprintln!(
                 "WARN: `rust.debug-assertions = true` will prevent downloading CI rustc as alt CI \
                 rustc is not currently built with debug assertions."
             );
             // We need to put this later down_ci_rustc_commit.
-            config.download_rustc_commit = None;
+            download_rustc_commit = None;
+        }
+
+        // We need to override `rust.channel` if it's manually specified when using the CI rustc.
+        // This is because if the compiler uses a different channel than the one specified in bootstrap.toml,
+        // tests may fail due to using a different channel than the one used by the compiler during tests.
+        if let Some(commit) = &download_rustc_commit
+            && is_user_configured_rust_channel
+        {
+            println!(
+                "WARNING: `rust.download-rustc` is enabled. The `rust.channel` option will be overridden by the CI rustc's channel."
+            );
+
+            channel =
+                read_file_by_commit(&dwn_ctx, &rust_info, Path::new("src/ci/channel"), commit)
+                    .trim()
+                    .to_owned();
         }
 
         if let Some(t) = toml.target {
@@ -969,24 +793,22 @@ impl Config {
                 let mut target = Target::from_triple(&triple);
 
                 if let Some(ref s) = cfg.llvm_config {
-                    if config.download_rustc_commit.is_some()
-                        && triple == *config.host_target.triple
-                    {
+                    if download_rustc_commit.is_some() && triple == *host_target.triple {
                         panic!(
                             "setting llvm_config for the host is incompatible with download-rustc"
                         );
                     }
-                    target.llvm_config = Some(config.src.join(s));
+                    target.llvm_config = Some(src.join(s));
                 }
                 if let Some(patches) = cfg.llvm_has_rust_patches {
                     assert!(
-                        config.submodules == Some(false) || cfg.llvm_config.is_some(),
+                        build_submodules == Some(false) || cfg.llvm_config.is_some(),
                         "use of `llvm-has-rust-patches` is restricted to cases where either submodules are disabled or llvm-config been provided"
                     );
                     target.llvm_has_rust_patches = Some(patches);
                 }
                 if let Some(ref s) = cfg.llvm_filecheck {
-                    target.llvm_filecheck = Some(config.src.join(s));
+                    target.llvm_filecheck = Some(src.join(s));
                 }
                 target.llvm_libunwind = cfg.llvm_libunwind.as_ref().map(|v| {
                     v.parse().unwrap_or_else(|_| {
@@ -1023,81 +845,17 @@ impl Config {
                     })
                 });
 
-                config.target_config.insert(TargetSelection::from_user(&triple), target);
+                target_config.insert(TargetSelection::from_user(&triple), target);
             }
         }
 
-        if rust_optimize.as_ref().is_some_and(|v| matches!(v, RustOptimize::Bool(false))) {
-            eprintln!(
-                "WARNING: setting `optimize` to `false` is known to cause errors and \
-                should be considered unsupported. Refer to `bootstrap.example.toml` \
-                for more details."
-            );
-        }
-
-        config.rust_new_symbol_mangling = rust_new_symbol_mangling;
-        set(&mut config.rust_optimize_tests, rust_optimize_tests);
-        set(&mut config.codegen_tests, rust_codegen_tests);
-        set(&mut config.rust_rpath, rust_rpath);
-        set(&mut config.rust_strip, rust_strip);
-        set(&mut config.rust_frame_pointers, rust_frame_pointers);
-        config.rust_stack_protector = rust_stack_protector;
-        set(&mut config.jemalloc, rust_jemalloc);
-        set(&mut config.test_compare_mode, rust_test_compare_mode);
-        set(&mut config.backtrace, rust_backtrace);
-        set(&mut config.rust_dist_src, rust_dist_src);
-        set(&mut config.verbose_tests, rust_verbose_tests);
-        // in the case "false" is set explicitly, do not overwrite the command line args
-        if let Some(true) = rust_incremental {
-            config.incremental = true;
-        }
-        set(&mut config.lld_mode, rust_lld_mode);
-        set(&mut config.llvm_bitcode_linker_enabled, rust_llvm_bitcode_linker);
-
-        config.rust_randomize_layout = rust_randomize_layout.unwrap_or_default();
-        config.llvm_tools_enabled = rust_llvm_tools.unwrap_or(true);
-
-        config.llvm_enzyme = config.channel == "dev" || config.channel == "nightly";
-        config.rustc_default_linker = rust_default_linker;
-        config.musl_root = rust_musl_root.map(PathBuf::from);
-        config.save_toolstates = rust_save_toolstates.map(PathBuf::from);
-        set(
-            &mut config.deny_warnings,
-            match flags_warnings {
-                Warnings::Deny => Some(true),
-                Warnings::Warn => Some(false),
-                Warnings::Default => rust_deny_warnings,
-            },
+        let llvm_from_ci = parse_download_ci_llvm(
+            &dwn_ctx,
+            &rust_info,
+            &download_rustc_commit,
+            llvm_download_ci_llvm,
+            llvm_assertions,
         );
-        set(&mut config.backtrace_on_ice, rust_backtrace_on_ice);
-        set(&mut config.rust_verify_llvm_ir, rust_verify_llvm_ir);
-        config.rust_thin_lto_import_instr_limit = rust_thin_lto_import_instr_limit;
-        set(&mut config.rust_remap_debuginfo, rust_remap_debuginfo);
-        set(&mut config.control_flow_guard, rust_control_flow_guard);
-        set(&mut config.ehcont_guard, rust_ehcont_guard);
-        config.llvm_libunwind_default =
-            rust_llvm_libunwind.map(|v| v.parse().expect("failed to parse rust.llvm-libunwind"));
-        set(
-            &mut config.rust_codegen_backends,
-            rust_codegen_backends.map(|backends| parse_codegen_backends(backends, "rust")),
-        );
-
-        config.rust_codegen_units = rust_codegen_units.map(threads_from_config);
-        config.rust_codegen_units_std = rust_codegen_units_std.map(threads_from_config);
-
-        if config.rust_profile_use.is_none() {
-            config.rust_profile_use = rust_profile_use;
-        }
-
-        if config.rust_profile_generate.is_none() {
-            config.rust_profile_generate = rust_profile_generate;
-        }
-
-        config.rust_lto =
-            rust_lto.as_deref().map(|value| RustcLto::from_str(value).unwrap()).unwrap_or_default();
-        config.rust_validate_mir_opts = rust_validate_mir_opts;
-
-        config.rust_optimize = rust_optimize.unwrap_or(RustOptimize::Bool(true));
 
         // We make `x86_64-unknown-linux-gnu` use the self-contained linker by default, so we will
         // build our internal lld and use it as the default linker, by setting the `rust.lld` config
@@ -1111,105 +869,17 @@ impl Config {
         //   thus, disabled
         // - similarly, lld will not be built nor used by default when explicitly asked not to, e.g.
         //   when the config sets `rust.lld = false`
-        if default_lld_opt_in_targets().contains(&config.host_target.triple.to_string())
-            && config.hosts == [config.host_target]
+        let lld_enabled = if default_lld_opt_in_targets().contains(&host_target.triple.to_string())
+            && hosts == [host_target]
         {
-            let no_llvm_config = config
-                .target_config
-                .get(&config.host_target)
-                .is_none_or(|target_config| target_config.llvm_config.is_none());
-            let enable_lld = config.llvm_from_ci || no_llvm_config;
-            // Prefer the config setting in case an explicit opt-out is needed.
-            config.lld_enabled = rust_lld_enabled.unwrap_or(enable_lld);
+            let no_llvm_config =
+                target_config.get(&host_target).is_none_or(|config| config.llvm_config.is_none());
+            rust_lld_enabled.unwrap_or(llvm_from_ci || no_llvm_config)
         } else {
-            set(&mut config.lld_enabled, rust_lld_enabled);
-        }
-
-        let default_std_features = BTreeSet::from([String::from("panic-unwind")]);
-        config.rust_std_features = rust_std_features.unwrap_or(default_std_features);
-
-        let default = rust_debug == Some(true);
-        config.rustc_debug_assertions = rust_rustc_debug_assertions.unwrap_or(default);
-        config.std_debug_assertions =
-            rust_std_debug_assertions.unwrap_or(config.rustc_debug_assertions);
-        config.tools_debug_assertions =
-            rust_tools_debug_assertions.unwrap_or(config.rustc_debug_assertions);
-        config.rust_overflow_checks = rust_overflow_checks.unwrap_or(default);
-        config.rust_overflow_checks_std =
-            rust_overflow_checks_std.unwrap_or(config.rust_overflow_checks);
-
-        config.rust_debug_logging = rust_debug_logging.unwrap_or(config.rustc_debug_assertions);
-
-        let with_defaults = |debuginfo_level_specific: Option<_>| {
-            debuginfo_level_specific.or(rust_debuginfo_level).unwrap_or(
-                if rust_debug == Some(true) {
-                    DebuginfoLevel::Limited
-                } else {
-                    DebuginfoLevel::None
-                },
-            )
+            rust_lld_enabled.unwrap_or(false)
         };
-        config.rust_debuginfo_level_rustc = with_defaults(rust_debuginfo_level_rustc);
-        config.rust_debuginfo_level_std = with_defaults(rust_debuginfo_level_std);
-        config.rust_debuginfo_level_tools = with_defaults(rust_debuginfo_level_tools);
-        config.rust_debuginfo_level_tests =
-            rust_debuginfo_level_tests.unwrap_or(DebuginfoLevel::None);
-
-        config.reproducible_artifacts = flags_reproducible_artifact;
-        config.description = build_description;
-
-        // We need to override `rust.channel` if it's manually specified when using the CI rustc.
-        // This is because if the compiler uses a different channel than the one specified in bootstrap.toml,
-        // tests may fail due to using a different channel than the one used by the compiler during tests.
-        if let Some(commit) = &config.download_rustc_commit
-            && is_user_configured_rust_channel
-        {
-            println!(
-                "WARNING: `rust.download-rustc` is enabled. The `rust.channel` option will be overridden by the CI rustc's channel."
-            );
 
-            let dwn_ctx = DownloadContext::from(&config);
-            let channel =
-                read_file_by_commit(dwn_ctx, Path::new("src/ci/channel"), commit).trim().to_owned();
-
-            config.channel = channel;
-        }
-
-        set(&mut config.ninja_in_file, llvm_ninja);
-        set(&mut config.llvm_optimize, llvm_optimize);
-        set(&mut config.llvm_thin_lto, llvm_thin_lto);
-        set(&mut config.llvm_release_debuginfo, llvm_release_debuginfo);
-        set(&mut config.llvm_static_stdcpp, llvm_static_libstdcpp);
-        set(&mut config.llvm_libzstd, llvm_libzstd);
-        if let Some(v) = llvm_link_shared {
-            config.llvm_link_shared.set(Some(v));
-        }
-        config.llvm_targets.clone_from(&llvm_targets);
-        config.llvm_experimental_targets.clone_from(&llvm_experimental_targets);
-        config.llvm_link_jobs = llvm_link_jobs;
-        config.llvm_version_suffix.clone_from(&llvm_version_suffix);
-        config.llvm_clang_cl.clone_from(&llvm_clang_cl);
-        config.llvm_tests = llvm_tests.unwrap_or_default();
-        config.llvm_enzyme = llvm_enzyme.unwrap_or_default();
-        config.llvm_plugins = llvm_plugin.unwrap_or_default();
-
-        config.llvm_cflags.clone_from(&llvm_cflags);
-        config.llvm_cxxflags.clone_from(&llvm_cxxflags);
-        config.llvm_ldflags.clone_from(&llvm_ldflags);
-        set(&mut config.llvm_use_libcxx, llvm_use_libcxx);
-        config.llvm_use_linker.clone_from(&llvm_use_linker);
-        config.llvm_allow_old_toolchain = llvm_allow_old_toolchain.unwrap_or(false);
-        config.llvm_offload = llvm_offload.unwrap_or(false);
-        config.llvm_polly = llvm_polly.unwrap_or(false);
-        config.llvm_clang = llvm_clang.unwrap_or(false);
-        config.llvm_enable_warnings = llvm_enable_warnings.unwrap_or(false);
-        config.llvm_build_config = llvm_build_config.clone().unwrap_or(Default::default());
-
-        let dwn_ctx = DownloadContext::from(&config);
-        config.llvm_from_ci =
-            parse_download_ci_llvm(dwn_ctx, llvm_download_ci_llvm, config.llvm_assertions);
-
-        if config.llvm_from_ci {
+        if llvm_from_ci {
             let warn = |option: &str| {
                 println!(
                     "WARNING: `{option}` will only be used on `compiler/rustc_llvm` build, not for the LLVM build."
@@ -1244,66 +914,21 @@ impl Config {
             }
         }
 
-        if !config.llvm_from_ci && config.llvm_thin_lto && llvm_link_shared.is_none() {
-            // If we're building with ThinLTO on, by default we want to link
-            // to LLVM shared, to avoid re-doing ThinLTO (which happens in
-            // the link step) with each stage.
-            config.llvm_link_shared.set(Some(true));
-        }
-
-        config.gcc_ci_mode = match gcc_download_ci_gcc {
-            Some(value) => match value {
-                true => GccCiMode::DownloadFromCi,
-                false => GccCiMode::BuildLocally,
-            },
-            None => GccCiMode::default(),
-        };
-
-        match build_ccache {
-            Some(StringOrBool::String(ref s)) => config.ccache = Some(s.to_string()),
-            Some(StringOrBool::Bool(true)) => {
-                config.ccache = Some("ccache".to_string());
-            }
-            Some(StringOrBool::Bool(false)) | None => {}
-        }
-
-        if config.llvm_from_ci {
-            let triple = &config.host_target.triple;
-            let dwn_ctx = DownloadContext::from(&config);
-            let ci_llvm_bin = ci_llvm_root(dwn_ctx).join("bin");
-            let build_target = config
-                .target_config
-                .entry(config.host_target)
-                .or_insert_with(|| Target::from_triple(triple));
-
+        if llvm_from_ci {
+            let triple = &host_target.triple;
+            let ci_llvm_bin = ci_llvm_root(&dwn_ctx, llvm_from_ci, &out).join("bin");
+            let build_target =
+                target_config.entry(host_target).or_insert_with(|| Target::from_triple(triple));
             check_ci_llvm!(build_target.llvm_config);
             check_ci_llvm!(build_target.llvm_filecheck);
-            build_target.llvm_config =
-                Some(ci_llvm_bin.join(exe("llvm-config", config.host_target)));
-            build_target.llvm_filecheck =
-                Some(ci_llvm_bin.join(exe("FileCheck", config.host_target)));
+            build_target.llvm_config = Some(ci_llvm_bin.join(exe("llvm-config", host_target)));
+            build_target.llvm_filecheck = Some(ci_llvm_bin.join(exe("FileCheck", host_target)));
         }
 
-        config.dist_sign_folder = dist_sign_folder.map(PathBuf::from);
-        config.dist_upload_addr = dist_upload_addr;
-        config.dist_compression_formats = dist_compression_formats;
-        set(&mut config.dist_compression_profile, dist_compression_profile);
-        set(&mut config.rust_dist_src, dist_src_tarball);
-        set(&mut config.dist_include_mingw_linker, dist_include_mingw_linker);
-        config.dist_vendor = dist_vendor.unwrap_or_else(|| {
-            // If we're building from git or tarball sources, enable it by default.
-            config.rust_info.is_managed_git_subrepository() || config.rust_info.is_from_tarball()
-        });
-
-        config.initial_rustfmt = if let Some(r) = build_rustfmt {
-            Some(r)
-        } else {
-            let dwn_ctx = DownloadContext::from(&config);
-            maybe_download_rustfmt(dwn_ctx)
-        };
+        let initial_rustfmt = build_rustfmt.or_else(|| maybe_download_rustfmt(&dwn_ctx, &out));
 
-        if matches!(config.lld_mode, LldMode::SelfContained)
-            && !config.lld_enabled
+        if matches!(rust_lld_mode.unwrap_or_default(), LldMode::SelfContained)
+            && !lld_enabled
             && flags_stage.unwrap_or(0) > 0
         {
             panic!(
@@ -1311,29 +936,13 @@ impl Config {
             );
         }
 
-        let dwn_ctx = DownloadContext::from(&config);
-        if config.lld_enabled && is_system_llvm(dwn_ctx, config.host_target) {
+        if lld_enabled && is_system_llvm(&dwn_ctx, &target_config, llvm_from_ci, host_target) {
             panic!("Cannot enable LLD with `rust.lld = true` when using external llvm-config.");
         }
 
-        config.optimized_compiler_builtins =
-            build_optimized_compiler_builtins.unwrap_or(config.channel != "dev");
-        config.compiletest_diff_tool = build_compiletest_diff_tool;
-        config.compiletest_use_stage0_libtest =
-            build_compiletest_use_stage0_libtest.unwrap_or(true);
-        config.tidy_extra_checks = build_tidy_extra_checks;
+        let download_rustc = download_rustc_commit.is_some();
 
-        let download_rustc = config.download_rustc_commit.is_some();
-        config.explicit_stage_from_cli = flags_stage.is_some();
-        config.explicit_stage_from_config = build_test_stage.is_some()
-            || build_build_stage.is_some()
-            || build_doc_stage.is_some()
-            || build_dist_stage.is_some()
-            || build_install_stage.is_some()
-            || build_check_stage.is_some()
-            || build_bench_stage.is_some();
-
-        config.stage = match config.cmd {
+        let stage = match flags_cmd {
             Subcommand::Check { .. } => flags_stage.or(build_check_stage).unwrap_or(1),
             Subcommand::Clippy { .. } | Subcommand::Fix => {
                 flags_stage.or(build_check_stage).unwrap_or(1)
@@ -1362,7 +971,7 @@ impl Config {
         };
 
         // Now check that the selected stage makes sense, and if not, print a warning and end
-        match (config.stage, &config.cmd) {
+        match (stage, &flags_cmd) {
             (0, Subcommand::Build { .. }) => {
                 eprintln!("ERROR: cannot build anything on stage 0. Use at least stage 1.");
                 exit!(1);
@@ -1382,7 +991,7 @@ impl Config {
             _ => {}
         }
 
-        if config.compile_time_deps && !matches!(config.cmd, Subcommand::Check { .. }) {
+        if flags_compile_time_deps && !matches!(flags_cmd, Subcommand::Check { .. }) {
             eprintln!(
                 "WARNING: Can't use --compile-time-deps with any subcommand other than check."
             );
@@ -1391,8 +1000,8 @@ impl Config {
 
         // CI should always run stage 2 builds, unless it specifically states otherwise
         #[cfg(not(test))]
-        if flags_stage.is_none() && config.is_running_on_ci {
-            match config.cmd {
+        if flags_stage.is_none() && is_running_on_ci {
+            match flags_cmd {
                 Subcommand::Test { .. }
                 | Subcommand::Miri { .. }
                 | Subcommand::Doc { .. }
@@ -1401,9 +1010,8 @@ impl Config {
                 | Subcommand::Dist
                 | Subcommand::Install => {
                     assert_eq!(
-                        config.stage, 2,
-                        "x.py should be run with `--stage 2` on CI, but was run with `--stage {}`",
-                        config.stage,
+                        stage, 2,
+                        "x.py should be run with `--stage 2` on CI, but was run with `--stage {stage}`",
                     );
                 }
                 Subcommand::Clean { .. }
@@ -1418,7 +1026,296 @@ impl Config {
             }
         }
 
-        config
+        let with_defaults = |debuginfo_level_specific: Option<_>| {
+            debuginfo_level_specific.or(rust_debuginfo_level).unwrap_or(
+                if rust_debug == Some(true) {
+                    DebuginfoLevel::Limited
+                } else {
+                    DebuginfoLevel::None
+                },
+            )
+        };
+
+        let ccache = match build_ccache {
+            Some(StringOrBool::String(s)) => Some(s),
+            Some(StringOrBool::Bool(true)) => Some("ccache".to_string()),
+            _ => None,
+        };
+
+        let explicit_stage_from_config = build_test_stage.is_some()
+            || build_build_stage.is_some()
+            || build_doc_stage.is_some()
+            || build_dist_stage.is_some()
+            || build_install_stage.is_some()
+            || build_check_stage.is_some()
+            || build_bench_stage.is_some();
+
+        let deny_warnings = match flags_warnings {
+            Warnings::Deny => true,
+            Warnings::Warn => false,
+            Warnings::Default => rust_deny_warnings.unwrap_or(true),
+        };
+
+        let gcc_ci_mode = match gcc_download_ci_gcc {
+            Some(value) => match value {
+                true => GccCiMode::DownloadFromCi,
+                false => GccCiMode::BuildLocally,
+            },
+            None => GccCiMode::default(),
+        };
+
+        let targets = flags_target
+            .map(|TargetSelectionList(targets)| targets)
+            .or_else(|| {
+                build_target.map(|t| t.iter().map(|t| TargetSelection::from_user(t)).collect())
+            })
+            .unwrap_or_else(|| hosts.clone());
+
+        #[allow(clippy::map_identity)]
+        let skip = flags_skip
+            .into_iter()
+            .chain(flags_exclude)
+            .chain(build_exclude.unwrap_or_default())
+            .map(|p| {
+                // Never return top-level path here as it would break `--skip`
+                // logic on rustc's internal test framework which is utilized by compiletest.
+                #[cfg(windows)]
+                {
+                    PathBuf::from(p.to_string_lossy().replace('/', "\\"))
+                }
+                #[cfg(not(windows))]
+                {
+                    p
+                }
+            })
+            .collect();
+
+        let cargo_info = git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/cargo"));
+        let clippy_info = git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/clippy"));
+        let in_tree_gcc_info = git_info(&exec_ctx, false, &src.join("src/gcc"));
+        let in_tree_llvm_info = git_info(&exec_ctx, false, &src.join("src/llvm-project"));
+        let enzyme_info = git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/enzyme"));
+        let miri_info = git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/miri"));
+        let rust_analyzer_info =
+            git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/rust-analyzer"));
+        let rustfmt_info = git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/rustfmt"));
+
+        let optimized_compiler_builtins =
+            build_optimized_compiler_builtins.unwrap_or(channel != "dev");
+        let vendor = build_vendor.unwrap_or(
+            rust_info.is_from_tarball()
+                && src.join("vendor").exists()
+                && src.join(".cargo/config.toml").exists(),
+        );
+        let verbose_tests = rust_verbose_tests.unwrap_or(exec_ctx.is_verbose());
+
+        Config {
+            // tidy-alphabetical-start
+            android_ndk: build_android_ndk,
+            backtrace: rust_backtrace.unwrap_or(true),
+            backtrace_on_ice: rust_backtrace_on_ice.unwrap_or(false),
+            bindir: install_bindir.map(PathBuf::from).unwrap_or("bin".into()),
+            bootstrap_cache_path: build_bootstrap_cache_path,
+            bypass_bootstrap_lock: flags_bypass_bootstrap_lock,
+            cargo_info,
+            cargo_native_static: build_cargo_native_static.unwrap_or(false),
+            ccache,
+            change_id: toml.change_id.inner,
+            channel,
+            clippy_info,
+            cmd: flags_cmd,
+            codegen_tests: rust_codegen_tests.unwrap_or(true),
+            color: flags_color,
+            compile_time_deps: flags_compile_time_deps,
+            compiler_docs: build_compiler_docs.unwrap_or(false),
+            compiletest_allow_stage0: build_compiletest_allow_stage0.unwrap_or(false),
+            compiletest_diff_tool: build_compiletest_diff_tool,
+            compiletest_use_stage0_libtest: build_compiletest_use_stage0_libtest.unwrap_or(true),
+            config: toml_path,
+            configure_args: build_configure_args.unwrap_or_default(),
+            control_flow_guard: rust_control_flow_guard.unwrap_or(false),
+            datadir: install_datadir.map(PathBuf::from),
+            deny_warnings,
+            description: build_description,
+            dist_compression_formats,
+            dist_compression_profile: dist_compression_profile.unwrap_or("fast".into()),
+            dist_include_mingw_linker: dist_include_mingw_linker.unwrap_or(true),
+            dist_sign_folder: dist_sign_folder.map(PathBuf::from),
+            dist_upload_addr,
+            dist_vendor: dist_vendor.unwrap_or_else(|| {
+                // If we're building from git or tarball sources, enable it by default.
+                rust_info.is_managed_git_subrepository() || rust_info.is_from_tarball()
+            }),
+            docdir: install_docdir.map(PathBuf::from),
+            docs: build_docs.unwrap_or(true),
+            docs_minification: build_docs_minification.unwrap_or(true),
+            download_rustc_commit,
+            dump_bootstrap_shims: flags_dump_bootstrap_shims,
+            ehcont_guard: rust_ehcont_guard.unwrap_or(false),
+            enable_bolt_settings: flags_enable_bolt_settings,
+            enzyme_info,
+            exec_ctx,
+            explicit_stage_from_cli: flags_stage.is_some(),
+            explicit_stage_from_config,
+            extended: build_extended.unwrap_or(false),
+            free_args: flags_free_args,
+            full_bootstrap: build_full_bootstrap.unwrap_or(false),
+            gcc_ci_mode,
+            gdb: build_gdb.map(PathBuf::from),
+            host_target,
+            hosts,
+            in_tree_gcc_info,
+            in_tree_llvm_info,
+            include_default_paths: flags_include_default_paths,
+            incremental: flags_incremental || rust_incremental == Some(true),
+            initial_cargo,
+            initial_cargo_clippy: build_cargo_clippy,
+            initial_rustc,
+            initial_rustfmt,
+            initial_sysroot,
+            is_running_on_ci,
+            jemalloc: rust_jemalloc.unwrap_or(false),
+            jobs: Some(threads_from_config(flags_jobs.or(build_jobs).unwrap_or(0))),
+            json_output: flags_json_output,
+            keep_stage: flags_keep_stage,
+            keep_stage_std: flags_keep_stage_std,
+            libdir: install_libdir.map(PathBuf::from),
+            library_docs_private_items: build_library_docs_private_items.unwrap_or(false),
+            lld_enabled,
+            lld_mode: rust_lld_mode.unwrap_or_default(),
+            lldb: build_lldb.map(PathBuf::from),
+            llvm_allow_old_toolchain: llvm_allow_old_toolchain.unwrap_or(false),
+            llvm_assertions,
+            llvm_bitcode_linker_enabled: rust_llvm_bitcode_linker.unwrap_or(false),
+            llvm_build_config: llvm_build_config.clone().unwrap_or(Default::default()),
+            llvm_cflags,
+            llvm_clang: llvm_clang.unwrap_or(false),
+            llvm_clang_cl,
+            llvm_cxxflags,
+            llvm_enable_warnings: llvm_enable_warnings.unwrap_or(false),
+            llvm_enzyme: llvm_enzyme.unwrap_or(false),
+            llvm_experimental_targets,
+            llvm_from_ci,
+            llvm_ldflags,
+            llvm_libunwind_default: rust_llvm_libunwind
+                .map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")),
+            llvm_libzstd: llvm_libzstd.unwrap_or(false),
+            llvm_link_jobs,
+            // If we're building with ThinLTO on, by default we want to link
+            // to LLVM shared, to avoid re-doing ThinLTO (which happens in
+            // the link step) with each stage.
+            llvm_link_shared: Cell::new(
+                llvm_link_shared
+                    .or((!llvm_from_ci && llvm_thin_lto.unwrap_or(false)).then_some(true)),
+            ),
+            llvm_offload: llvm_offload.unwrap_or(false),
+            llvm_optimize: llvm_optimize.unwrap_or(true),
+            llvm_plugins: llvm_plugin.unwrap_or(false),
+            llvm_polly: llvm_polly.unwrap_or(false),
+            llvm_profile_generate: flags_llvm_profile_generate,
+            llvm_profile_use: flags_llvm_profile_use,
+            llvm_release_debuginfo: llvm_release_debuginfo.unwrap_or(false),
+            llvm_static_stdcpp: llvm_static_libstdcpp.unwrap_or(false),
+            llvm_targets,
+            llvm_tests: llvm_tests.unwrap_or(false),
+            llvm_thin_lto: llvm_thin_lto.unwrap_or(false),
+            llvm_tools_enabled: rust_llvm_tools.unwrap_or(true),
+            llvm_use_libcxx: llvm_use_libcxx.unwrap_or(false),
+            llvm_use_linker,
+            llvm_version_suffix,
+            local_rebuild: build_local_rebuild.unwrap_or(false),
+            locked_deps: build_locked_deps.unwrap_or(false),
+            low_priority: build_low_priority.unwrap_or(false),
+            mandir: install_mandir.map(PathBuf::from),
+            miri_info,
+            musl_root: rust_musl_root.map(PathBuf::from),
+            ninja_in_file: llvm_ninja.unwrap_or(true),
+            nodejs: build_nodejs.map(PathBuf::from),
+            npm: build_npm.map(PathBuf::from),
+            omit_git_hash,
+            on_fail: flags_on_fail,
+            optimized_compiler_builtins,
+            out,
+            patch_binaries_for_nix: build_patch_binaries_for_nix,
+            path_modification_cache,
+            paths: flags_paths,
+            prefix: install_prefix.map(PathBuf::from),
+            print_step_rusage: build_print_step_rusage.unwrap_or(false),
+            print_step_timings: build_print_step_timings.unwrap_or(false),
+            profiler: build_profiler.unwrap_or(false),
+            python: build_python.map(PathBuf::from),
+            reproducible_artifacts: flags_reproducible_artifact,
+            reuse: build_reuse.map(PathBuf::from),
+            rust_analyzer_info,
+            rust_codegen_backends: rust_codegen_backends
+                .map(|backends| parse_codegen_backends(backends, "rust"))
+                .unwrap_or(vec![CodegenBackendKind::Llvm]),
+            rust_codegen_units: rust_codegen_units.map(threads_from_config),
+            rust_codegen_units_std: rust_codegen_units_std.map(threads_from_config),
+            rust_debug_logging: rust_debug_logging
+                .or(rust_rustc_debug_assertions)
+                .unwrap_or(rust_debug == Some(true)),
+            rust_debuginfo_level_rustc: with_defaults(rust_debuginfo_level_rustc),
+            rust_debuginfo_level_std: with_defaults(rust_debuginfo_level_std),
+            rust_debuginfo_level_tests: rust_debuginfo_level_tests.unwrap_or(DebuginfoLevel::None),
+            rust_debuginfo_level_tools: with_defaults(rust_debuginfo_level_tools),
+            rust_dist_src: dist_src_tarball.unwrap_or_else(|| rust_dist_src.unwrap_or(true)),
+            rust_frame_pointers: rust_frame_pointers.unwrap_or(false),
+            rust_info,
+            rust_lto: rust_lto
+                .as_deref()
+                .map(|value| RustcLto::from_str(value).unwrap())
+                .unwrap_or_default(),
+            rust_new_symbol_mangling,
+            rust_optimize: rust_optimize.unwrap_or(RustOptimize::Bool(true)),
+            rust_optimize_tests: rust_optimize_tests.unwrap_or(true),
+            rust_overflow_checks: rust_overflow_checks.unwrap_or(rust_debug == Some(true)),
+            rust_overflow_checks_std: rust_overflow_checks_std
+                .or(rust_overflow_checks)
+                .unwrap_or(rust_debug == Some(true)),
+            rust_profile_generate: flags_rust_profile_generate.or(rust_profile_generate),
+            rust_profile_use: flags_rust_profile_use.or(rust_profile_use),
+            rust_randomize_layout: rust_randomize_layout.unwrap_or(false),
+            rust_remap_debuginfo: rust_remap_debuginfo.unwrap_or(false),
+            rust_rpath: rust_rpath.unwrap_or(true),
+            rust_stack_protector,
+            rust_std_features: rust_std_features
+                .unwrap_or(BTreeSet::from([String::from("panic-unwind")])),
+            rust_strip: rust_strip.unwrap_or(false),
+            rust_thin_lto_import_instr_limit,
+            rust_validate_mir_opts,
+            rust_verify_llvm_ir: rust_verify_llvm_ir.unwrap_or(false),
+            rustc_debug_assertions: rust_rustc_debug_assertions.unwrap_or(rust_debug == Some(true)),
+            rustc_default_linker: rust_default_linker,
+            rustc_error_format: flags_rustc_error_format,
+            rustfmt_info,
+            sanitizers: build_sanitizers.unwrap_or(false),
+            save_toolstates: rust_save_toolstates.map(PathBuf::from),
+            skip,
+            skip_std_check_if_no_download_rustc: flags_skip_std_check_if_no_download_rustc,
+            src,
+            stage,
+            stage0_metadata,
+            std_debug_assertions: rust_std_debug_assertions
+                .or(rust_rustc_debug_assertions)
+                .unwrap_or(rust_debug == Some(true)),
+            stderr_is_tty: std::io::stderr().is_terminal(),
+            stdout_is_tty: std::io::stdout().is_terminal(),
+            submodules: build_submodules,
+            sysconfdir: install_sysconfdir.map(PathBuf::from),
+            target_config,
+            targets,
+            test_compare_mode: rust_test_compare_mode.unwrap_or(false),
+            tidy_extra_checks: build_tidy_extra_checks,
+            tool: build_tool.unwrap_or_default(),
+            tools: build_tools,
+            tools_debug_assertions: rust_tools_debug_assertions
+                .or(rust_rustc_debug_assertions)
+                .unwrap_or(rust_debug == Some(true)),
+            vendor,
+            verbose_tests,
+            // tidy-alphabetical-end
+        }
     }
 
     pub fn dry_run(&self) -> bool {
@@ -1456,7 +1353,7 @@ impl Config {
     /// Returns the content of the given file at a specific commit.
     pub(crate) fn read_file_by_commit(&self, file: &Path, commit: &str) -> String {
         let dwn_ctx = DownloadContext::from(self);
-        read_file_by_commit(dwn_ctx, file, commit)
+        read_file_by_commit(dwn_ctx, &self.rust_info, file, commit)
     }
 
     /// Bootstrap embeds a version number into the name of shared libraries it uploads in CI.
@@ -1528,7 +1425,7 @@ impl Config {
     /// The absolute path to the downloaded LLVM artifacts.
     pub(crate) fn ci_llvm_root(&self) -> PathBuf {
         let dwn_ctx = DownloadContext::from(self);
-        ci_llvm_root(dwn_ctx)
+        ci_llvm_root(dwn_ctx, self.llvm_from_ci, &self.out)
     }
 
     /// Directory where the extracted `rustc-dev` component is stored.
@@ -1692,7 +1589,7 @@ impl Config {
     )]
     pub(crate) fn update_submodule(&self, relative_path: &str) {
         let dwn_ctx = DownloadContext::from(self);
-        update_submodule(dwn_ctx, relative_path);
+        update_submodule(dwn_ctx, &self.rust_info, relative_path);
     }
 
     /// Returns true if any of the `paths` have been modified locally.
@@ -1755,8 +1652,8 @@ impl Config {
 
     /// Returns the codegen backend that should be configured as the *default* codegen backend
     /// for a rustc compiled by bootstrap.
-    pub fn default_codegen_backend(&self, target: TargetSelection) -> Option<CodegenBackendKind> {
-        self.enabled_codegen_backends(target).first().cloned()
+    pub fn default_codegen_backend(&self, target: TargetSelection) -> Option<&CodegenBackendKind> {
+        self.enabled_codegen_backends(target).first()
     }
 
     pub fn jemalloc(&self, target: TargetSelection) -> bool {
@@ -1808,7 +1705,7 @@ impl Config {
     /// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set.
     pub fn is_system_llvm(&self, target: TargetSelection) -> bool {
         let dwn_ctx = DownloadContext::from(self);
-        is_system_llvm(dwn_ctx, target)
+        is_system_llvm(dwn_ctx, &self.target_config, self.llvm_from_ci, target)
     }
 
     /// Returns `true` if this is our custom, patched, version of LLVM.
@@ -2102,6 +1999,7 @@ pub fn check_stage0_version(
 
 pub fn download_ci_rustc_commit<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    rust_info: &channel::GitInfo,
     download_rustc: Option<StringOrBool>,
     llvm_assertions: bool,
 ) -> Option<String> {
@@ -2121,7 +2019,7 @@ pub fn download_ci_rustc_commit<'a>(
         None | Some(StringOrBool::Bool(false)) => return None,
         Some(StringOrBool::Bool(true)) => false,
         Some(StringOrBool::String(s)) if s == "if-unchanged" => {
-            if !dwn_ctx.rust_info.is_managed_git_subrepository() {
+            if !rust_info.is_managed_git_subrepository() {
                 println!(
                     "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources."
                 );
@@ -2135,7 +2033,7 @@ pub fn download_ci_rustc_commit<'a>(
         }
     };
 
-    let commit = if dwn_ctx.rust_info.is_managed_git_subrepository() {
+    let commit = if rust_info.is_managed_git_subrepository() {
         // 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);
@@ -2209,6 +2107,8 @@ pub fn git_config(stage0_metadata: &build_helper::stage0_parser::Stage0) -> GitC
 
 pub fn parse_download_ci_llvm<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    rust_info: &channel::GitInfo,
+    download_rustc_commit: &Option<String>,
     download_ci_llvm: Option<StringOrBool>,
     asserts: bool,
 ) -> bool {
@@ -2224,7 +2124,7 @@ pub fn parse_download_ci_llvm<'a>(
     let download_ci_llvm = download_ci_llvm.unwrap_or(default);
 
     let if_unchanged = || {
-        if dwn_ctx.rust_info.is_from_tarball() {
+        if rust_info.is_from_tarball() {
             // Git is needed for running "if-unchanged" logic.
             println!("ERROR: 'if-unchanged' is only compatible with Git managed sources.");
             crate::exit!(1);
@@ -2232,7 +2132,7 @@ pub fn parse_download_ci_llvm<'a>(
 
         // Fetching the LLVM submodule is unnecessary for self-tests.
         #[cfg(not(test))]
-        update_submodule(dwn_ctx, "src/llvm-project");
+        update_submodule(dwn_ctx, rust_info, "src/llvm-project");
 
         // Check for untracked changes in `src/llvm-project` and other important places.
         let has_changes = has_changes_from_upstream(dwn_ctx, LLVM_INVALIDATION_PATHS);
@@ -2247,7 +2147,7 @@ pub fn parse_download_ci_llvm<'a>(
 
     match download_ci_llvm {
         StringOrBool::Bool(b) => {
-            if !b && dwn_ctx.download_rustc_commit.is_some() {
+            if !b && download_rustc_commit.is_some() {
                 panic!(
                     "`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`."
                 );
@@ -2290,9 +2190,13 @@ pub fn has_changes_from_upstream<'a>(
         fields(relative_path = ?relative_path),
     ),
 )]
-pub(crate) fn update_submodule<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>, relative_path: &str) {
+pub(crate) fn update_submodule<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    rust_info: &channel::GitInfo,
+    relative_path: &str,
+) {
     let dwn_ctx = dwn_ctx.as_ref();
-    if dwn_ctx.rust_info.is_from_tarball() || !submodules_(dwn_ctx.submodules, dwn_ctx.rust_info) {
+    if rust_info.is_from_tarball() || !submodules_(dwn_ctx.submodules, rust_info) {
         return;
     }
 
@@ -2421,12 +2325,14 @@ pub fn submodules_(submodules: &Option<bool>, rust_info: &channel::GitInfo) -> b
 /// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set.
 pub fn is_system_llvm<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    target_config: &HashMap<TargetSelection, Target>,
+    llvm_from_ci: bool,
     target: TargetSelection,
 ) -> bool {
     let dwn_ctx = dwn_ctx.as_ref();
-    match dwn_ctx.target_config.get(&target) {
+    match target_config.get(&target) {
         Some(Target { llvm_config: Some(_), .. }) => {
-            let ci_llvm = dwn_ctx.llvm_from_ci && is_host_target(&dwn_ctx.host_target, &target);
+            let ci_llvm = llvm_from_ci && is_host_target(&dwn_ctx.host_target, &target);
             !ci_llvm
         }
         // We're building from the in-tree src/llvm-project sources.
@@ -2439,21 +2345,26 @@ pub fn is_host_target(host_target: &TargetSelection, target: &TargetSelection) -
     host_target == target
 }
 
-pub(crate) fn ci_llvm_root<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>) -> PathBuf {
+pub(crate) fn ci_llvm_root<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    llvm_from_ci: bool,
+    out: &Path,
+) -> PathBuf {
     let dwn_ctx = dwn_ctx.as_ref();
-    assert!(dwn_ctx.llvm_from_ci);
-    dwn_ctx.out.join(dwn_ctx.host_target).join("ci-llvm")
+    assert!(llvm_from_ci);
+    out.join(dwn_ctx.host_target).join("ci-llvm")
 }
 
 /// Returns the content of the given file at a specific commit.
 pub(crate) fn read_file_by_commit<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    rust_info: &channel::GitInfo,
     file: &Path,
     commit: &str,
 ) -> String {
     let dwn_ctx = dwn_ctx.as_ref();
     assert!(
-        dwn_ctx.rust_info.is_managed_git_subrepository(),
+        rust_info.is_managed_git_subrepository(),
         "`Config::read_file_by_commit` is not supported in non-git sources."
     );
 
diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs
index 17bfb388280..c01b71b9260 100644
--- a/src/bootstrap/src/core/config/flags.rs
+++ b/src/bootstrap/src/core/config/flags.rs
@@ -15,7 +15,7 @@ use crate::core::build_steps::setup::Profile;
 use crate::core::builder::{Builder, Kind};
 use crate::core::config::Config;
 use crate::core::config::target_selection::{TargetSelectionList, target_selection_list};
-use crate::{Build, DocTests};
+use crate::{Build, CodegenBackendKind, DocTests};
 
 #[derive(Copy, Clone, Default, Debug, ValueEnum)]
 pub enum Color {
@@ -419,6 +419,9 @@ pub enum Subcommand {
         #[arg(long)]
         /// don't capture stdout/stderr of tests
         no_capture: bool,
+        #[arg(long)]
+        /// Use a different codegen backend when running tests.
+        test_codegen_backend: Option<CodegenBackendKind>,
     },
     /// Build and run some test suites *in Miri*
     Miri {
@@ -658,6 +661,13 @@ impl Subcommand {
             _ => vec![],
         }
     }
+
+    pub fn test_codegen_backend(&self) -> Option<&CodegenBackendKind> {
+        match self {
+            Subcommand::Test { test_codegen_backend, .. } => test_codegen_backend.as_ref(),
+            _ => None,
+        }
+    }
 }
 
 /// Returns the shell completion for a given shell, if the result differs from the current
diff --git a/src/bootstrap/src/core/config/mod.rs b/src/bootstrap/src/core/config/mod.rs
index 285d20917e7..dbd05fd2519 100644
--- a/src/bootstrap/src/core/config/mod.rs
+++ b/src/bootstrap/src/core/config/mod.rs
@@ -324,7 +324,7 @@ impl FromStr for LlvmLibunwind {
     }
 }
 
-#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum SplitDebuginfo {
     Packed,
     Unpacked,
@@ -402,12 +402,6 @@ pub enum GccCiMode {
     DownloadFromCi,
 }
 
-pub fn set<T>(field: &mut T, val: Option<T>) {
-    if let Some(v) = val {
-        *field = v;
-    }
-}
-
 pub fn threads_from_config(v: u32) -> u32 {
     match v {
         0 => std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32,
diff --git a/src/bootstrap/src/core/config/target_selection.rs b/src/bootstrap/src/core/config/target_selection.rs
index ebd3fe7a8c6..40b63a7f9c7 100644
--- a/src/bootstrap/src/core/config/target_selection.rs
+++ b/src/bootstrap/src/core/config/target_selection.rs
@@ -14,7 +14,7 @@ pub struct TargetSelection {
 }
 
 /// Newtype over `Vec<TargetSelection>` so we can implement custom parsing logic
-#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+#[derive(Clone, Default, PartialEq, Eq, Hash, Debug)]
 pub struct TargetSelectionList(pub Vec<TargetSelection>);
 
 pub fn target_selection_list(s: &str) -> Result<TargetSelectionList, String> {
diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs
index 5ded44cef14..2f3c80559c0 100644
--- a/src/bootstrap/src/core/download.rs
+++ b/src/bootstrap/src/core/download.rs
@@ -9,9 +9,8 @@ use std::sync::{Arc, Mutex, OnceLock};
 use build_helper::git::PathFreshness;
 use xz2::bufread::XzDecoder;
 
-use crate::core::config::{BUILDER_CONFIG_FILENAME, Target, TargetSelection};
+use crate::core::config::{BUILDER_CONFIG_FILENAME, TargetSelection};
 use crate::utils::build_stamp::BuildStamp;
-use crate::utils::channel;
 use crate::utils::exec::{ExecutionContext, command};
 use crate::utils::helpers::{exe, hex_encode, move_file};
 use crate::{Config, t};
@@ -73,7 +72,7 @@ impl Config {
 
     fn download_file(&self, url: &str, dest_path: &Path, help_on_error: &str) {
         let dwn_ctx: DownloadContext<'_> = self.into();
-        download_file(dwn_ctx, url, dest_path, help_on_error);
+        download_file(dwn_ctx, &self.out, url, dest_path, help_on_error);
     }
 
     fn unpack(&self, tarball: &Path, dst: &Path, pattern: &str) {
@@ -238,7 +237,7 @@ impl Config {
         destination: &str,
     ) {
         let dwn_ctx: DownloadContext<'_> = self.into();
-        download_component(dwn_ctx, mode, filename, prefix, key, destination);
+        download_component(dwn_ctx, &self.out, mode, filename, prefix, key, destination);
     }
 
     #[cfg(test)]
@@ -403,13 +402,8 @@ impl Config {
 pub(crate) struct DownloadContext<'a> {
     pub path_modification_cache: Arc<Mutex<HashMap<Vec<&'static str>, PathFreshness>>>,
     pub src: &'a Path,
-    pub rust_info: &'a channel::GitInfo,
     pub submodules: &'a Option<bool>,
-    pub download_rustc_commit: &'a Option<String>,
     pub host_target: TargetSelection,
-    pub llvm_from_ci: bool,
-    pub target_config: &'a HashMap<TargetSelection, Target>,
-    pub out: &'a Path,
     pub patch_binaries_for_nix: Option<bool>,
     pub exec_ctx: &'a ExecutionContext,
     pub stage0_metadata: &'a build_helper::stage0_parser::Stage0,
@@ -430,12 +424,7 @@ impl<'a> From<&'a Config> for DownloadContext<'a> {
             path_modification_cache: value.path_modification_cache.clone(),
             src: &value.src,
             host_target: value.host_target,
-            rust_info: &value.rust_info,
-            download_rustc_commit: &value.download_rustc_commit,
             submodules: &value.submodules,
-            llvm_from_ci: value.llvm_from_ci,
-            target_config: &value.target_config,
-            out: &value.out,
             patch_binaries_for_nix: value.patch_binaries_for_nix,
             exec_ctx: &value.exec_ctx,
             stage0_metadata: &value.stage0_metadata,
@@ -495,6 +484,7 @@ pub(crate) fn is_download_ci_available(target_triple: &str, llvm_assertions: boo
 #[cfg(test)]
 pub(crate) fn maybe_download_rustfmt<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    out: &Path,
 ) -> Option<PathBuf> {
     Some(PathBuf::new())
 }
@@ -504,6 +494,7 @@ pub(crate) fn maybe_download_rustfmt<'a>(
 #[cfg(not(test))]
 pub(crate) fn maybe_download_rustfmt<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    out: &Path,
 ) -> Option<PathBuf> {
     use build_helper::stage0_parser::VersionMetadata;
 
@@ -517,7 +508,7 @@ pub(crate) fn maybe_download_rustfmt<'a>(
     let channel = format!("{version}-{date}");
 
     let host = dwn_ctx.host_target;
-    let bin_root = dwn_ctx.out.join(host).join("rustfmt");
+    let bin_root = out.join(host).join("rustfmt");
     let rustfmt_path = bin_root.join("bin").join(exe("rustfmt", host));
     let rustfmt_stamp = BuildStamp::new(&bin_root).with_prefix("rustfmt").add_stamp(channel);
     if rustfmt_path.exists() && rustfmt_stamp.is_up_to_date() {
@@ -526,6 +517,7 @@ pub(crate) fn maybe_download_rustfmt<'a>(
 
     download_component(
         dwn_ctx,
+        out,
         DownloadSource::Dist,
         format!("rustfmt-{version}-{build}.tar.xz", build = host.triple),
         "rustfmt-preview",
@@ -535,6 +527,7 @@ pub(crate) fn maybe_download_rustfmt<'a>(
 
     download_component(
         dwn_ctx,
+        out,
         DownloadSource::Dist,
         format!("rustc-{version}-{build}.tar.xz", build = host.triple),
         "rustc",
@@ -543,13 +536,13 @@ pub(crate) fn maybe_download_rustfmt<'a>(
     );
 
     if should_fix_bins_and_dylibs(dwn_ctx.patch_binaries_for_nix, dwn_ctx.exec_ctx) {
-        fix_bin_or_dylib(dwn_ctx.out, &bin_root.join("bin").join("rustfmt"), dwn_ctx.exec_ctx);
-        fix_bin_or_dylib(dwn_ctx.out, &bin_root.join("bin").join("cargo-fmt"), dwn_ctx.exec_ctx);
+        fix_bin_or_dylib(out, &bin_root.join("bin").join("rustfmt"), dwn_ctx.exec_ctx);
+        fix_bin_or_dylib(out, &bin_root.join("bin").join("cargo-fmt"), dwn_ctx.exec_ctx);
         let lib_dir = bin_root.join("lib");
         for lib in t!(fs::read_dir(&lib_dir), lib_dir.display().to_string()) {
             let lib = t!(lib);
             if path_is_dylib(&lib.path()) {
-                fix_bin_or_dylib(dwn_ctx.out, &lib.path(), dwn_ctx.exec_ctx);
+                fix_bin_or_dylib(out, &lib.path(), dwn_ctx.exec_ctx);
             }
         }
     }
@@ -559,10 +552,10 @@ pub(crate) fn maybe_download_rustfmt<'a>(
 }
 
 #[cfg(test)]
-pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>) {}
+pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>, out: &Path) {}
 
 #[cfg(not(test))]
-pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>) {
+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(|| {
         println!("downloading stage0 beta artifacts");
@@ -574,6 +567,7 @@ pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a
     let sysroot = "stage0";
     download_toolchain(
         dwn_ctx,
+        out,
         &version,
         sysroot,
         &date,
@@ -583,8 +577,10 @@ pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a
     );
 }
 
+#[allow(clippy::too_many_arguments)]
 fn download_toolchain<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    out: &Path,
     version: &str,
     sysroot: &str,
     stamp_key: &str,
@@ -594,7 +590,7 @@ fn download_toolchain<'a>(
 ) {
     let dwn_ctx = dwn_ctx.as_ref();
     let host = dwn_ctx.host_target.triple;
-    let bin_root = dwn_ctx.out.join(host).join(sysroot);
+    let bin_root = out.join(host).join(sysroot);
     let rustc_stamp = BuildStamp::new(&bin_root).with_prefix("rustc").add_stamp(stamp_key);
 
     if !bin_root.join("bin").join(exe("rustc", dwn_ctx.host_target)).exists()
@@ -605,20 +601,28 @@ fn download_toolchain<'a>(
         }
         let filename = format!("rust-std-{version}-{host}.tar.xz");
         let pattern = format!("rust-std-{host}");
-        download_component(dwn_ctx, mode.clone(), filename, &pattern, stamp_key, destination);
+        download_component(dwn_ctx, out, mode.clone(), filename, &pattern, stamp_key, destination);
         let filename = format!("rustc-{version}-{host}.tar.xz");
-        download_component(dwn_ctx, mode.clone(), filename, "rustc", stamp_key, destination);
+        download_component(dwn_ctx, out, mode.clone(), filename, "rustc", stamp_key, destination);
 
         for component in extra_components {
             let filename = format!("{component}-{version}-{host}.tar.xz");
-            download_component(dwn_ctx, mode.clone(), filename, component, stamp_key, destination);
+            download_component(
+                dwn_ctx,
+                out,
+                mode.clone(),
+                filename,
+                component,
+                stamp_key,
+                destination,
+            );
         }
 
         if should_fix_bins_and_dylibs(dwn_ctx.patch_binaries_for_nix, dwn_ctx.exec_ctx) {
-            fix_bin_or_dylib(dwn_ctx.out, &bin_root.join("bin").join("rustc"), dwn_ctx.exec_ctx);
-            fix_bin_or_dylib(dwn_ctx.out, &bin_root.join("bin").join("rustdoc"), dwn_ctx.exec_ctx);
+            fix_bin_or_dylib(out, &bin_root.join("bin").join("rustc"), dwn_ctx.exec_ctx);
+            fix_bin_or_dylib(out, &bin_root.join("bin").join("rustdoc"), dwn_ctx.exec_ctx);
             fix_bin_or_dylib(
-                dwn_ctx.out,
+                out,
                 &bin_root.join("libexec").join("rust-analyzer-proc-macro-srv"),
                 dwn_ctx.exec_ctx,
             );
@@ -626,7 +630,7 @@ fn download_toolchain<'a>(
             for lib in t!(fs::read_dir(&lib_dir), lib_dir.display().to_string()) {
                 let lib = t!(lib);
                 if path_is_dylib(&lib.path()) {
-                    fix_bin_or_dylib(dwn_ctx.out, &lib.path(), dwn_ctx.exec_ctx);
+                    fix_bin_or_dylib(out, &lib.path(), dwn_ctx.exec_ctx);
                 }
             }
         }
@@ -750,6 +754,7 @@ fn should_fix_bins_and_dylibs(
 
 fn download_component<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    out: &Path,
     mode: DownloadSource,
     filename: String,
     prefix: &str,
@@ -763,14 +768,14 @@ fn download_component<'a>(
     }
 
     let cache_dst =
-        dwn_ctx.bootstrap_cache_path.as_ref().cloned().unwrap_or_else(|| dwn_ctx.out.join("cache"));
+        dwn_ctx.bootstrap_cache_path.as_ref().cloned().unwrap_or_else(|| out.join("cache"));
 
     let cache_dir = cache_dst.join(key);
     if !cache_dir.exists() {
         t!(fs::create_dir_all(&cache_dir));
     }
 
-    let bin_root = dwn_ctx.out.join(dwn_ctx.host_target).join(destination);
+    let bin_root = out.join(dwn_ctx.host_target).join(destination);
     let tarball = cache_dir.join(&filename);
     let (base_url, url, should_verify) = match mode {
         DownloadSource::CI => {
@@ -835,7 +840,7 @@ HELP: if trying to compile an old commit of rustc, disable `download-rustc` in b
 download-rustc = false
 ";
     }
-    download_file(dwn_ctx, &format!("{base_url}/{url}"), &tarball, help_on_error);
+    download_file(dwn_ctx, out, &format!("{base_url}/{url}"), &tarball, help_on_error);
     if let Some(sha256) = checksum
         && !verify(dwn_ctx.exec_ctx, &tarball, sha256)
     {
@@ -953,6 +958,7 @@ fn unpack(exec_ctx: &ExecutionContext, tarball: &Path, dst: &Path, pattern: &str
 
 fn download_file<'a>(
     dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    out: &Path,
     url: &str,
     dest_path: &Path,
     help_on_error: &str,
@@ -963,7 +969,7 @@ fn download_file<'a>(
         println!("download {url}");
     });
     // Use a temporary file in case we crash while downloading, to avoid a corrupt download in cache/.
-    let tempfile = tempdir(dwn_ctx.out).join(dest_path.file_name().unwrap());
+    let tempfile = tempdir(out).join(dest_path.file_name().unwrap());
     // While bootstrap itself only supports http and https downloads, downstream forks might
     // need to download components from other protocols. The match allows them adding more
     // protocols without worrying about merge conflicts if we change the HTTP implementation.
diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs
index bd02131b7fe..de7cada93f2 100644
--- a/src/bootstrap/src/core/sanity.rs
+++ b/src/bootstrap/src/core/sanity.rs
@@ -35,6 +35,7 @@ pub struct Finder {
 const STAGE0_MISSING_TARGETS: &[&str] = &[
     "armv7a-vex-v5",
     // just a dummy comment so the list doesn't get onelined
+    "aarch64_be-unknown-hermit",
     "aarch64_be-unknown-none-softfloat",
 ];
 
@@ -327,6 +328,23 @@ than building it.
             .entry(*target)
             .or_insert_with(|| Target::from_triple(&target.triple));
 
+        // compiler-rt c fallbacks for wasm cannot be built with gcc
+        if target.contains("wasm")
+            && (build.config.optimized_compiler_builtins(*target)
+                || build.config.rust_std_features.contains("compiler-builtins-c"))
+        {
+            let cc_tool = build.cc_tool(*target);
+            if !cc_tool.is_like_clang() && !cc_tool.path().ends_with("emcc") {
+                // emcc works as well
+                panic!(
+                    "Clang is required to build C code for Wasm targets, got `{}` instead\n\
+                    this is because compiler-builtins is configured to build C source. Either \
+                    ensure Clang is used, or adjust this configuration.",
+                    cc_tool.path().display()
+                );
+            }
+        }
+
         if (target.contains("-none-") || target.contains("nvptx"))
             && build.no_std(*target) == Some(false)
         {
diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index 706a3cbb210..ec7edbf7531 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -165,6 +165,20 @@ impl CodegenBackendKind {
     }
 }
 
+impl std::str::FromStr for CodegenBackendKind {
+    type Err = &'static str;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.to_lowercase().as_str() {
+            "" => Err("Invalid empty backend name"),
+            "gcc" => Ok(Self::Gcc),
+            "llvm" => Ok(Self::Llvm),
+            "cranelift" => Ok(Self::Cranelift),
+            _ => Ok(Self::Custom(s.to_string())),
+        }
+    }
+}
+
 #[derive(PartialEq, Eq, Copy, Clone, Debug)]
 pub enum DocTests {
     /// Run normal tests and doc tests (default).
@@ -279,7 +293,7 @@ pub enum DependencyType {
 ///
 /// These entries currently correspond to the various output directories of the
 /// build system, with each mod generating output in a different directory.
-#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
 pub enum Mode {
     /// Build the standard library, placing output in the "stageN-std" directory.
     Std,
@@ -357,7 +371,7 @@ pub enum RemapScheme {
     NonCompiler,
 }
 
-#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
 pub enum CLang {
     C,
     Cxx,
@@ -1743,6 +1757,7 @@ impl Build {
     ///
     /// If `src` is a symlink, `src` will be resolved to the actual path
     /// and copied to `dst` instead of the symlink itself.
+    #[track_caller]
     pub fn resolve_symlink_and_copy(&self, src: &Path, dst: &Path) {
         self.copy_link_internal(src, dst, true);
     }
@@ -1751,6 +1766,7 @@ impl Build {
     /// Attempts to use hard links if possible, falling back to copying.
     /// You can neither rely on this being a copy nor it being a link,
     /// so do not write to dst.
+    #[track_caller]
     pub fn copy_link(&self, src: &Path, dst: &Path, file_type: FileType) {
         self.copy_link_internal(src, dst, false);
 
@@ -1765,6 +1781,7 @@ impl Build {
         }
     }
 
+    #[track_caller]
     fn copy_link_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) {
         if self.config.dry_run() {
             return;
@@ -1773,6 +1790,10 @@ impl Build {
         if src == dst {
             return;
         }
+
+        #[cfg(feature = "tracing")]
+        let _span = trace_io!("file-copy-link", ?src, ?dst);
+
         if let Err(e) = fs::remove_file(dst)
             && cfg!(windows)
             && e.kind() != io::ErrorKind::NotFound
@@ -1815,6 +1836,7 @@ impl Build {
     /// Links the `src` directory recursively to `dst`. Both are assumed to exist
     /// when this function is called.
     /// Will attempt to use hard links if possible and fall back to copying.
+    #[track_caller]
     pub fn cp_link_r(&self, src: &Path, dst: &Path) {
         if self.config.dry_run() {
             return;
@@ -1837,12 +1859,14 @@ impl Build {
     /// Will attempt to use hard links if possible and fall back to copying.
     /// Unwanted files or directories can be skipped
     /// by returning `false` from the filter function.
+    #[track_caller]
     pub fn cp_link_filtered(&self, src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) {
         // Immediately recurse with an empty relative path
         self.cp_link_filtered_recurse(src, dst, Path::new(""), filter)
     }
 
     // Inner function does the actual work
+    #[track_caller]
     fn cp_link_filtered_recurse(
         &self,
         src: &Path,
@@ -1862,7 +1886,6 @@ impl Build {
                     self.create_dir(&dst);
                     self.cp_link_filtered_recurse(&path, &dst, &relative, filter);
                 } else {
-                    let _ = fs::remove_file(&dst);
                     self.copy_link(&path, &dst, FileType::Regular);
                 }
             }
@@ -1904,10 +1927,15 @@ impl Build {
         t!(fs::read_to_string(path))
     }
 
+    #[track_caller]
     fn create_dir(&self, dir: &Path) {
         if self.config.dry_run() {
             return;
         }
+
+        #[cfg(feature = "tracing")]
+        let _span = trace_io!("dir-create", ?dir);
+
         t!(fs::create_dir_all(dir))
     }
 
@@ -1915,6 +1943,10 @@ impl Build {
         if self.config.dry_run() {
             return;
         }
+
+        #[cfg(feature = "tracing")]
+        let _span = trace_io!("dir-remove", ?dir);
+
         t!(fs::remove_dir_all(dir))
     }
 
diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs
index b454a8ddefb..4fb5891ed18 100644
--- a/src/bootstrap/src/utils/change_tracker.rs
+++ b/src/bootstrap/src/utils/change_tracker.rs
@@ -506,4 +506,14 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
         severity: ChangeSeverity::Warning,
         summary: "It is no longer possible to `x clippy` with stage 0. All clippy commands have to be on stage 1+.",
     },
+    ChangeInfo {
+        change_id: 145256,
+        severity: ChangeSeverity::Info,
+        summary: "Added `--test-codegen-backend` CLI option for tests",
+    },
+    ChangeInfo {
+        change_id: 145379,
+        severity: ChangeSeverity::Info,
+        summary: "Build/check now supports forwarding `--timings` flag to cargo.",
+    },
 ];
diff --git a/src/bootstrap/src/utils/tracing.rs b/src/bootstrap/src/utils/tracing.rs
index 428ba013c98..472781ffa73 100644
--- a/src/bootstrap/src/utils/tracing.rs
+++ b/src/bootstrap/src/utils/tracing.rs
@@ -49,13 +49,36 @@ macro_rules! error {
 }
 
 #[cfg(feature = "tracing")]
+pub const IO_SPAN_TARGET: &str = "IO";
+
+/// Create a tracing span around an I/O operation, if tracing is enabled.
+/// Note that at least one tracing value field has to be passed to this macro, otherwise it will not
+/// compile.
+#[macro_export]
+macro_rules! trace_io {
+    ($name:expr, $($args:tt)*) => {
+        ::tracing::trace_span!(
+            target: $crate::utils::tracing::IO_SPAN_TARGET,
+            $name,
+            $($args)*,
+            location = $crate::utils::tracing::format_location(*::std::panic::Location::caller())
+        ).entered()
+    }
+}
+
+#[cfg(feature = "tracing")]
+pub fn format_location(location: std::panic::Location<'static>) -> String {
+    format!("{}:{}", location.file(), location.line())
+}
+
+#[cfg(feature = "tracing")]
 const COMMAND_SPAN_TARGET: &str = "COMMAND";
 
 #[cfg(feature = "tracing")]
 pub fn trace_cmd(command: &crate::BootstrapCommand) -> tracing::span::EnteredSpan {
     let fingerprint = command.fingerprint();
     let location = command.get_created_location();
-    let location = format!("{}:{}", location.file(), location.line());
+    let location = format_location(location);
 
     tracing::span!(
         target: COMMAND_SPAN_TARGET,
@@ -84,6 +107,7 @@ mod inner {
     use std::fmt::Debug;
     use std::fs::File;
     use std::io::Write;
+    use std::path::{Path, PathBuf};
     use std::sync::atomic::Ordering;
 
     use chrono::{DateTime, Utc};
@@ -93,8 +117,8 @@ mod inner {
     use tracing_subscriber::registry::{LookupSpan, SpanRef};
     use tracing_subscriber::{EnvFilter, Layer};
 
+    use super::{COMMAND_SPAN_TARGET, IO_SPAN_TARGET};
     use crate::STEP_SPAN_TARGET;
-    use crate::utils::tracing::COMMAND_SPAN_TARGET;
 
     pub fn setup_tracing(env_name: &str) -> TracingGuard {
         let filter = EnvFilter::from_env(env_name);
@@ -291,6 +315,23 @@ mod inner {
                 Ok(())
             }
 
+            // Write fields while treating the "location" field specially, and assuming that it
+            // contains the source file location relevant to the span.
+            let write_with_location = |writer: &mut W| -> std::io::Result<()> {
+                if let Some(values) = field_values {
+                    write_fields(
+                        writer,
+                        values.fields.iter().filter(|(name, _)| *name != "location"),
+                    )?;
+                    let location =
+                        &values.fields.iter().find(|(name, _)| *name == "location").unwrap().1;
+                    let (filename, line) = location.rsplit_once(':').unwrap();
+                    let filename = shorten_filename(filename);
+                    write!(writer, " ({filename}:{line})",)?;
+                }
+                Ok(())
+            };
+
             // We handle steps specially. We instrument them dynamically in `Builder::ensure`,
             // and we want to have custom name for each step span. But tracing doesn't allow setting
             // dynamic span names. So we detect step spans here and override their name.
@@ -311,17 +352,11 @@ mod inner {
                 // Executed command
                 COMMAND_SPAN_TARGET => {
                     write!(writer, "{}", span.name())?;
-                    if let Some(values) = field_values {
-                        write_fields(
-                            writer,
-                            values.fields.iter().filter(|(name, _)| *name != "location"),
-                        )?;
-                        write!(
-                            writer,
-                            " ({})",
-                            values.fields.iter().find(|(name, _)| *name == "location").unwrap().1
-                        )?;
-                    }
+                    write_with_location(writer)?;
+                }
+                IO_SPAN_TARGET => {
+                    write!(writer, "{}", span.name())?;
+                    write_with_location(writer)?;
                 }
                 // Other span
                 _ => {
@@ -342,21 +377,10 @@ mod inner {
         writer: &mut W,
         metadata: &'static tracing::Metadata<'static>,
     ) -> std::io::Result<()> {
-        use std::path::{Path, PathBuf};
-
         if let Some(filename) = metadata.file() {
-            // Keep only the module name and file name to make it shorter
-            let filename: PathBuf = Path::new(filename)
-                .components()
-                // Take last two path components
-                .rev()
-                .take(2)
-                .collect::<Vec<_>>()
-                .into_iter()
-                .rev()
-                .collect();
-
-            write!(writer, " ({}", filename.display())?;
+            let filename = shorten_filename(filename);
+
+            write!(writer, " ({filename}")?;
             if let Some(line) = metadata.line() {
                 write!(writer, ":{line}")?;
             }
@@ -365,6 +389,21 @@ mod inner {
         Ok(())
     }
 
+    /// Keep only the module name and file name to make it shorter
+    fn shorten_filename(filename: &str) -> String {
+        Path::new(filename)
+            .components()
+            // Take last two path components
+            .rev()
+            .take(2)
+            .collect::<Vec<_>>()
+            .into_iter()
+            .rev()
+            .collect::<PathBuf>()
+            .display()
+            .to_string()
+    }
+
     impl<S> Layer<S> for TracingPrinter
     where
         S: Subscriber,
diff --git a/src/build_helper/src/util.rs b/src/build_helper/src/util.rs
index a8355f774e9..1bdbb7515e2 100644
--- a/src/build_helper/src/util.rs
+++ b/src/build_helper/src/util.rs
@@ -3,6 +3,8 @@ use std::io::{BufRead, BufReader};
 use std::path::Path;
 use std::process::Command;
 
+use crate::ci::CiEnv;
+
 /// Invokes `build_helper::util::detail_exit` with `cfg!(test)`
 ///
 /// This is a macro instead of a function so that it uses `cfg(test)` in the *calling* crate, not in build helper.
@@ -20,6 +22,15 @@ pub fn detail_exit(code: i32, is_test: bool) -> ! {
     if is_test {
         panic!("status code: {code}");
     } else {
+        // If we're in CI, print the current bootstrap invocation command, to make it easier to
+        // figure out what exactly has failed.
+        if CiEnv::is_ci() {
+            // Skip the first argument, as it will be some absolute path to the bootstrap binary.
+            let bootstrap_args =
+                std::env::args().skip(1).map(|a| a.to_string()).collect::<Vec<_>>().join(" ");
+            eprintln!("Bootstrap failed while executing `{bootstrap_args}`");
+        }
+
         // otherwise, exit with provided status code
         std::process::exit(code);
     }
diff --git a/src/ci/citool/src/analysis.rs b/src/ci/citool/src/analysis.rs
index 62974be2dbe..8ba8f1ab564 100644
--- a/src/ci/citool/src/analysis.rs
+++ b/src/ci/citool/src/analysis.rs
@@ -75,7 +75,7 @@ fn format_build_step_diffs(current: &BuildStep, parent: &BuildStep) -> String {
         }
     }
 
-    fn get_steps(step: &BuildStep) -> Vec<StepByName> {
+    fn get_steps(step: &BuildStep) -> Vec<StepByName<'_>> {
         step.linearize_steps().into_iter().map(|v| StepByName(v)).collect()
     }
 
diff --git a/src/ci/citool/src/test_dashboard.rs b/src/ci/citool/src/test_dashboard.rs
index 8fbd0d3f200..c9de38852e5 100644
--- a/src/ci/citool/src/test_dashboard.rs
+++ b/src/ci/citool/src/test_dashboard.rs
@@ -33,7 +33,7 @@ fn write_page<T: Template>(dir: &Path, name: &str, template: &T) -> anyhow::Resu
     Ok(())
 }
 
-fn gather_test_suites(job_metrics: &HashMap<JobName, JobMetrics>) -> TestSuites {
+fn gather_test_suites(job_metrics: &HashMap<JobName, JobMetrics>) -> TestSuites<'_> {
     struct CoarseTestSuite<'a> {
         tests: BTreeMap<String, Test<'a>>,
     }
diff --git a/src/ci/citool/src/utils.rs b/src/ci/citool/src/utils.rs
index 0367d349a1e..3176cb62f60 100644
--- a/src/ci/citool/src/utils.rs
+++ b/src/ci/citool/src/utils.rs
@@ -31,6 +31,6 @@ where
 }
 
 /// Normalizes Windows-style path delimiters to Unix-style paths.
-pub fn normalize_path_delimiters(name: &str) -> Cow<str> {
+pub fn normalize_path_delimiters(name: &str) -> Cow<'_, str> {
     if name.contains("\\") { name.replace('\\', "/").into() } else { name.into() }
 }
diff --git a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile
index 5c459e5cd18..4d5980027ca 100644
--- a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile
+++ b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile
@@ -55,6 +55,12 @@ RUN ./install-riscv64-none-elf.sh
 COPY host-x86_64/dist-various-1/install-riscv32-none-elf.sh /build
 RUN ./install-riscv32-none-elf.sh
 
+COPY host-x86_64/dist-various-1/install-emscripten.sh /build
+RUN ./install-emscripten.sh
+
+# Add Emscripten to PATH
+ENV PATH="/build/emsdk:/build/emsdk/upstream/emscripten:/build/emsdk/node/current/bin:${PATH}"
+
 # Suppress some warnings in the openwrt toolchains we downloaded
 ENV STAGING_DIR=/tmp
 
diff --git a/src/ci/docker/host-x86_64/dist-various-1/install-emscripten.sh b/src/ci/docker/host-x86_64/dist-various-1/install-emscripten.sh
new file mode 100755
index 00000000000..eeb54ca67f7
--- /dev/null
+++ b/src/ci/docker/host-x86_64/dist-various-1/install-emscripten.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+set -ex
+
+apt-get update
+apt-get install -y --no-install-recommends \
+  nodejs \
+  default-jre
+
+git clone https://github.com/emscripten-core/emsdk.git
+cd emsdk
+./emsdk install latest
+./emsdk activate latest
diff --git a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
index 0855ea222a3..33d55123936 100644
--- a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
+++ b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
@@ -59,6 +59,10 @@ ENV \
     CXX_i686_unknown_uefi=clang++-11 \
     CC_x86_64_unknown_uefi=clang-11 \
     CXX_x86_64_unknown_uefi=clang++-11 \
+    CC_wasm32_unknown_unknown=clang-11 \
+    CXX_wasm32_unknown_unknown=clang++-11 \
+    CC_wasm32v1_none=clang-11 \
+    CXX_wasm32v1_none=clang++-11 \
     CC=gcc-9 \
     CXX=g++-9
 
diff --git a/src/ci/docker/host-x86_64/test-various/Dockerfile b/src/ci/docker/host-x86_64/test-various/Dockerfile
index 82a820c859d..6ff529c9e71 100644
--- a/src/ci/docker/host-x86_64/test-various/Dockerfile
+++ b/src/ci/docker/host-x86_64/test-various/Dockerfile
@@ -4,6 +4,7 @@ ARG DEBIAN_FRONTEND=noninteractive
 RUN apt-get update && apt-get install -y --no-install-recommends \
   clang-11 \
   llvm-11 \
+  gcc-multilib \
   g++ \
   make \
   ninja-build \
@@ -59,8 +60,8 @@ RUN curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v19.0
   tar -xJ
 ENV PATH "$PATH:/wasmtime-v19.0.0-x86_64-linux"
 
-ENV WASM_TARGETS=wasm32-wasip1
-ENV WASM_SCRIPT python3 /checkout/x.py --stage 2 test --host='' --target $WASM_TARGETS \
+ENV WASM_WASIP_TARGET=wasm32-wasip1 
+ENV WASM_WASIP_SCRIPT python3 /checkout/x.py --stage 2 test --host='' --target $WASM_WASIP_TARGET \
   tests/run-make \
   tests/ui \
   tests/mir-opt \
@@ -91,4 +92,4 @@ ENV UEFI_SCRIPT python3 /checkout/x.py --stage 2 build --host='' --target $UEFI_
   python3 /checkout/x.py --stage 2 test tests/run-make/uefi-qemu/rmake.rs --target i686-unknown-uefi && \
   python3 /checkout/x.py --stage 2 test tests/run-make/uefi-qemu/rmake.rs --target x86_64-unknown-uefi
 
-ENV SCRIPT $WASM_SCRIPT && $NVPTX_SCRIPT && $MUSL_SCRIPT && $UEFI_SCRIPT
+ENV SCRIPT $WASM_WASIP_SCRIPT && $NVPTX_SCRIPT && $MUSL_SCRIPT && $UEFI_SCRIPT
diff --git a/src/ci/docker/host-x86_64/tidy/Dockerfile b/src/ci/docker/host-x86_64/tidy/Dockerfile
index ee1ae5410ee..c8558689d3b 100644
--- a/src/ci/docker/host-x86_64/tidy/Dockerfile
+++ b/src/ci/docker/host-x86_64/tidy/Dockerfile
@@ -45,4 +45,4 @@ RUN bash -c 'npm install -g eslint@$(cat /tmp/eslint.version)'
 # NOTE: intentionally uses python2 for x.py so we can test it still works.
 # validate-toolstate only runs in our CI, so it's ok for it to only support python3.
 ENV SCRIPT TIDY_PRINT_DIFF=1 python2.7 ../x.py test --stage 0 \
-  src/tools/tidy tidyselftest --extra-checks=py,cpp,js
+  src/tools/tidy tidyselftest --extra-checks=py,cpp,js,spellcheck
diff --git a/src/ci/docker/host-x86_64/tidy/eslint.version b/src/ci/docker/host-x86_64/tidy/eslint.version
index 1acea15afd6..42890ac0095 100644
--- a/src/ci/docker/host-x86_64/tidy/eslint.version
+++ b/src/ci/docker/host-x86_64/tidy/eslint.version
@@ -1 +1 @@
-8.6.0
\ No newline at end of file
+8.57.1
diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml
index ae13d14c380..409d2cba821 100644
--- a/src/ci/github-actions/jobs.yml
+++ b/src/ci/github-actions/jobs.yml
@@ -23,16 +23,11 @@ runners:
     <<: *base-job
 
   - &job-macos
-    os: macos-13
-    <<: *base-job
-
-  - &job-macos-m1
     os: macos-14
     <<: *base-job
 
   - &job-windows
     os: windows-2025
-    free_disk: true
     <<: *base-job
 
   - &job-windows-8c
@@ -68,17 +63,6 @@ runners:
     <<: *base-job
 
 envs:
-  env-x86_64-apple-tests: &env-x86_64-apple-tests
-    SCRIPT: ./x.py check compiletest --set build.compiletest-use-stage0-libtest=true && ./x.py --stage 2 test --skip tests/ui --skip tests/rustdoc -- --exact
-    RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc
-    # Ensure that host tooling is tested on our minimum supported macOS version.
-    MACOSX_DEPLOYMENT_TARGET: 10.12
-    MACOSX_STD_DEPLOYMENT_TARGET: 10.12
-    SELECT_XCODE: /Applications/Xcode_15.2.app
-    NO_LLVM_ASSERTIONS: 1
-    NO_DEBUG_ASSERTIONS: 1
-    NO_OVERFLOW_CHECKS: 1
-
   production:
     &production
     DEPLOY_BUCKET: rust-lang-ci2
@@ -455,8 +439,19 @@ auto:
 
   - name: dist-x86_64-apple
     env:
-      SCRIPT: ./x.py dist bootstrap --include-default-paths --host=x86_64-apple-darwin --target=x86_64-apple-darwin
-      RUST_CONFIGURE_ARGS: --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc --set rust.lto=thin --set rust.codegen-units=1
+      SCRIPT: >-
+        ./x.py dist bootstrap
+        --include-default-paths
+        --host=x86_64-apple-darwin
+        --target=x86_64-apple-darwin
+      RUST_CONFIGURE_ARGS: >-
+        --enable-full-tools
+        --enable-sanitizers
+        --enable-profiler
+        --disable-docs
+        --set rust.jemalloc
+        --set rust.lto=thin
+        --set rust.codegen-units=1
       # Ensure that host tooling is built to support our minimum support macOS version.
       MACOSX_DEPLOYMENT_TARGET: 10.12
       MACOSX_STD_DEPLOYMENT_TARGET: 10.12
@@ -482,17 +477,6 @@ auto:
       NO_LLVM_ASSERTIONS: 1
       NO_DEBUG_ASSERTIONS: 1
       NO_OVERFLOW_CHECKS: 1
-    <<: *job-macos-m1
-
-  - name: x86_64-apple-1
-    env:
-      <<: *env-x86_64-apple-tests
-    <<: *job-macos
-
-  - name: x86_64-apple-2
-    env:
-      SCRIPT: ./x.py --stage 2 test tests/ui tests/rustdoc
-      <<: *env-x86_64-apple-tests
     <<: *job-macos
 
   - name: dist-aarch64-apple
@@ -517,7 +501,7 @@ auto:
       NO_OVERFLOW_CHECKS: 1
       DIST_REQUIRE_ALL_TOOLS: 1
       CODEGEN_BACKENDS: llvm,cranelift
-    <<: *job-macos-m1
+    <<: *job-macos
 
   - name: aarch64-apple
     env:
@@ -537,7 +521,7 @@ auto:
       NO_LLVM_ASSERTIONS: 1
       NO_DEBUG_ASSERTIONS: 1
       NO_OVERFLOW_CHECKS: 1
-    <<: *job-macos-m1
+    <<: *job-macos
 
   ######################
   #  Windows Builders  #
diff --git a/src/ci/scripts/free-disk-space-windows-start.py b/src/ci/scripts/free-disk-space-windows-start.py
deleted file mode 100644
index fbaad722bff..00000000000
--- a/src/ci/scripts/free-disk-space-windows-start.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-Start freeing disk space on Windows in the background by launching
-the PowerShell cleanup script, and recording the PID in a file,
-so later steps can wait for completion.
-"""
-
-import subprocess
-from pathlib import Path
-from free_disk_space_windows_util import get_pid_file, get_log_file, run_main
-
-
-def get_cleanup_script() -> Path:
-    script_dir = Path(__file__).resolve().parent
-    cleanup_script = script_dir / "free-disk-space-windows.ps1"
-    if not cleanup_script.exists():
-        raise Exception(f"Cleanup script '{cleanup_script}' not found")
-    return cleanup_script
-
-
-def write_pid(pid: int):
-    pid_file = get_pid_file()
-    if pid_file.exists():
-        raise Exception(f"Pid file '{pid_file}' already exists")
-    pid_file.write_text(str(pid))
-    print(f"wrote pid {pid} in file {pid_file}")
-
-
-def launch_cleanup_process():
-    cleanup_script = get_cleanup_script()
-    log_file_path = get_log_file()
-    # Launch the PowerShell cleanup in the background and redirect logs.
-    try:
-        with open(log_file_path, "w", encoding="utf-8") as log_file:
-            proc = subprocess.Popen(
-                [
-                    "pwsh",
-                    # Suppress PowerShell startup banner/logo for cleaner logs.
-                    "-NoLogo",
-                    # Don't load user/system profiles. Ensures a clean, predictable environment.
-                    "-NoProfile",
-                    # Disable interactive prompts. Required for CI to avoid hangs.
-                    "-NonInteractive",
-                    # Execute the specified script file (next argument).
-                    "-File",
-                    str(cleanup_script),
-                ],
-                # Write child stdout to the log file.
-                stdout=log_file,
-                # Merge stderr into stdout for a single, ordered log stream.
-                stderr=subprocess.STDOUT,
-            )
-            print(
-                f"Started free-disk-space cleanup in background. "
-                f"pid={proc.pid}; log_file={log_file_path}"
-            )
-            return proc
-    except FileNotFoundError as e:
-        raise Exception("pwsh not found on PATH; cannot start disk cleanup.") from e
-
-
-def main() -> int:
-    proc = launch_cleanup_process()
-
-    # Write pid of the process to a file, so that later steps can read it and wait
-    # until the process completes.
-    write_pid(proc.pid)
-
-    return 0
-
-
-if __name__ == "__main__":
-    run_main(main)
diff --git a/src/ci/scripts/free-disk-space-windows-wait.py b/src/ci/scripts/free-disk-space-windows-wait.py
deleted file mode 100644
index b8612bb71c2..00000000000
--- a/src/ci/scripts/free-disk-space-windows-wait.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""
-Wait for the background Windows disk cleanup process.
-"""
-
-import ctypes
-import time
-from free_disk_space_windows_util import get_pid_file, get_log_file, run_main
-
-
-def is_process_running(pid: int) -> bool:
-    PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
-    processHandle = ctypes.windll.kernel32.OpenProcess(
-        PROCESS_QUERY_LIMITED_INFORMATION, 0, pid
-    )
-    if processHandle == 0:
-        # The process is not running.
-        # If you don't have the sufficient rights to check if a process is running,
-        # zero is also returned. But in GitHub Actions we have these rights.
-        return False
-    else:
-        ctypes.windll.kernel32.CloseHandle(processHandle)
-        return True
-
-
-def print_logs():
-    """Print the logs from the cleanup script."""
-    log_file = get_log_file()
-    if log_file.exists():
-        print("free-disk-space logs:")
-        # Print entire log; replace undecodable bytes to avoid exceptions.
-        try:
-            with open(log_file, "r", encoding="utf-8", errors="replace") as f:
-                print(f.read())
-        except Exception as e:
-            raise Exception(f"Failed to read log file '{log_file}'") from e
-    else:
-        print(f"::warning::Log file '{log_file}' not found")
-
-
-def read_pid_from_file() -> int:
-    """Read the PID from the pid file."""
-
-    pid_file = get_pid_file()
-    if not pid_file.exists():
-        raise Exception(
-            f"No background free-disk-space process to wait for: pid file {pid_file} not found"
-        )
-
-    pid_file_content = pid_file.read_text().strip()
-
-    # Delete the file if it exists
-    pid_file.unlink(missing_ok=True)
-
-    try:
-        # Read the first line and convert to int.
-        pid = int(pid_file_content.splitlines()[0])
-        return pid
-    except Exception as e:
-        raise Exception(
-            f"Error while parsing the pid file with content '{pid_file_content!r}'"
-        ) from e
-
-
-def main() -> int:
-    pid = read_pid_from_file()
-
-    # Poll until process exits
-    while is_process_running(pid):
-        time.sleep(3)
-
-    print_logs()
-
-    return 0
-
-
-if __name__ == "__main__":
-    run_main(main)
diff --git a/src/ci/scripts/free-disk-space-windows.ps1 b/src/ci/scripts/free-disk-space-windows.ps1
deleted file mode 100644
index 8a4677bd2ab..00000000000
--- a/src/ci/scripts/free-disk-space-windows.ps1
+++ /dev/null
@@ -1,35 +0,0 @@
-# Free disk space on Windows GitHub action runners.
-
-$ErrorActionPreference = 'Stop'
-
-Get-Volume | Out-String | Write-Output
-
-$available = $(Get-Volume C).SizeRemaining
-
-$dirs = 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm',
-'C:\rtools45', 'C:\ghcup', 'C:\Program Files (x86)\Android',
-'C:\Program Files\Google\Chrome', 'C:\Program Files (x86)\Microsoft\Edge',
-'C:\Program Files\Mozilla Firefox', 'C:\Program Files\MySQL', 'C:\Julia',
-'C:\Program Files\MongoDB', 'C:\Program Files\Azure Cosmos DB Emulator',
-'C:\Program Files\PostgreSQL', 'C:\Program Files\Unity Hub',
-'C:\Strawberry', 'C:\hostedtoolcache\windows\Java_Temurin-Hotspot_jdk'
-
-foreach ($dir in $dirs) {
-    Start-ThreadJob -InputObject $dir {
-        Remove-Item -Recurse -Force -LiteralPath $input
-    } | Out-Null
-}
-
-foreach ($job in Get-Job) {
-    Wait-Job $job  | Out-Null
-    if ($job.Error) {
-        Write-Output "::warning file=$PSCommandPath::$($job.Error)"
-    }
-    Remove-Job $job
-}
-
-Get-Volume | Out-String | Write-Output
-
-$saved = ($(Get-Volume C).SizeRemaining - $available) / 1gb
-$savedRounded = [math]::Round($saved, 3)
-Write-Output "total space saved: $savedRounded GB"
diff --git a/src/ci/scripts/free-disk-space.sh b/src/ci/scripts/free-disk-space.sh
deleted file mode 100755
index 9264fe4de6d..00000000000
--- a/src/ci/scripts/free-disk-space.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-
-script_dir=$(dirname "$0")
-
-if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
-    python3 "$script_dir/free-disk-space-windows-start.py"
-else
-    $script_dir/free-disk-space-linux.sh
-fi
diff --git a/src/ci/scripts/free_disk_space_windows_util.py b/src/ci/scripts/free_disk_space_windows_util.py
deleted file mode 100644
index 488187864c2..00000000000
--- a/src/ci/scripts/free_disk_space_windows_util.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-Utilities for Windows disk space cleanup scripts.
-"""
-
-import os
-from pathlib import Path
-import sys
-
-
-def get_temp_dir() -> Path:
-    """Get the temporary directory set by GitHub Actions."""
-    return Path(os.environ.get("RUNNER_TEMP"))
-
-
-def get_pid_file() -> Path:
-    return get_temp_dir() / "free-disk-space.pid"
-
-
-def get_log_file() -> Path:
-    return get_temp_dir() / "free-disk-space.log"
-
-
-def run_main(main_fn):
-    exit_code = 1
-    try:
-        exit_code = main_fn()
-    except Exception as e:
-        print(f"::error::{e}")
-    sys.exit(exit_code)
diff --git a/src/doc/rustc-dev-guide/src/autodiff/internals.md b/src/doc/rustc-dev-guide/src/autodiff/internals.md
index c1b31a0e4bd..c8e304f814b 100644
--- a/src/doc/rustc-dev-guide/src/autodiff/internals.md
+++ b/src/doc/rustc-dev-guide/src/autodiff/internals.md
@@ -17,7 +17,7 @@ fn main() {
 
 The detailed documentation for the `std::autodiff` module is available at [std::autodiff](https://doc.rust-lang.org/std/autodiff/index.html).
 
-Differentiable programing is used in various fields like numerical computing, [solid mechanics][ratel], [computational chemistry][molpipx], [fluid dynamics][waterlily] or for Neural Network training via Backpropagation, [ODE solver][diffsol], [differentiable rendering][libigl], [quantum computing][catalyst], and climate simulations.
+Differentiable programming is used in various fields like numerical computing, [solid mechanics][ratel], [computational chemistry][molpipx], [fluid dynamics][waterlily] or for Neural Network training via Backpropagation, [ODE solver][diffsol], [differentiable rendering][libigl], [quantum computing][catalyst], and climate simulations.
 
 [ratel]: https://gitlab.com/micromorph/ratel
 [molpipx]: https://arxiv.org/abs/2411.17011v
diff --git a/src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md b/src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md
index fb90c0fdb43..93b11c0690a 100644
--- a/src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md
+++ b/src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md
@@ -81,9 +81,11 @@ There are two orthogonal ways to control which kind of tracing logs you want:
    - If you select a level, all events/spans with an equal or higher priority level will be shown.
 2. You can also control the log **target**, e.g. `bootstrap` or `bootstrap::core::config` or a custom target like `CONFIG_HANDLING` or `STEP`.
     - Custom targets are used to limit what kinds of spans you are interested in, as the `BOOTSTRAP_TRACING=trace` output can be quite verbose. Currently, you can use the following custom targets:
-        - `CONFIG_HANDLING`: show spans related to config handling
-        - `STEP`: show all executed steps. Note that executed commands have `info` event level.
-        - `COMMAND`: show all executed commands. Note that executed commands have `trace` event level.
+        - `CONFIG_HANDLING`: show spans related to config handling.
+        - `STEP`: show all executed steps. Executed commands have `info` event level.
+        - `COMMAND`: show all executed commands. Executed commands have `trace` event level.
+        - `IO`: show performed I/O operations. Executed commands have `trace` event level.
+            - Note that many I/O are currently not being traced.
 
 You can of course combine them (custom target logs are typically gated behind `TRACE` log level additionally):
 
diff --git a/src/doc/rustc-dev-guide/src/sanitizers.md b/src/doc/rustc-dev-guide/src/sanitizers.md
index 29d9056c15d..34c78d4d952 100644
--- a/src/doc/rustc-dev-guide/src/sanitizers.md
+++ b/src/doc/rustc-dev-guide/src/sanitizers.md
@@ -45,7 +45,7 @@ implementation:
    [marked][sanitizer-attribute] with appropriate LLVM attribute:
    `SanitizeAddress`, `SanitizeHWAddress`, `SanitizeMemory`, or
    `SanitizeThread`. By default all functions are instrumented, but this
-   behaviour can be changed with `#[no_sanitize(...)]`.
+   behaviour can be changed with `#[sanitize(xyz = "on|off")]`.
 
 *  The decision whether to perform instrumentation or not is possible only at a
    function granularity. In the cases were those decision differ between
diff --git a/src/doc/rustc-dev-guide/src/solve/candidate-preference.md b/src/doc/rustc-dev-guide/src/solve/candidate-preference.md
index 89605294735..8b28f56760a 100644
--- a/src/doc/rustc-dev-guide/src/solve/candidate-preference.md
+++ b/src/doc/rustc-dev-guide/src/solve/candidate-preference.md
@@ -95,7 +95,7 @@ fn overflow<T: Trait>() {
 ```
 
 This preference causes a lot of issues. See [#24066]. Most of the
-issues are caused by prefering where-bounds over impls even if the where-bound guides type inference:
+issues are caused by preferring where-bounds over impls even if the where-bound guides type inference:
 ```rust
 trait Trait<T> {
     fn call_me(&self, x: T) {}
diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md
index f4ba9a044e6..fbbeb7e97d3 100644
--- a/src/doc/rustc-dev-guide/src/tests/directives.md
+++ b/src/doc/rustc-dev-guide/src/tests/directives.md
@@ -111,6 +111,7 @@ for more details.
 | `forbid-output`                   | A pattern which must not appear in stderr/`cfail` output                                                                 | `ui`, `incremental`                          | Regex pattern                                                                           |
 | `run-flags`                       | Flags passed to the test executable                                                                                      | `ui`                                         | Arbitrary flags                                                                         |
 | `known-bug`                       | No error annotation needed due to known bug                                                                              | `ui`, `crashes`, `incremental`               | Issue number `#123456`                                                                  |
+| `compare-output-by-lines`         | Compare the output by lines, rather than as a single string                                                              | All                                          | N/A                                                                                     |
 
 [^check_stdout]: presently <!-- date-check: Oct 2024 --> this has a weird quirk
     where the test binary's stdout and stderr gets concatenated and then
diff --git a/src/doc/rustc-dev-guide/src/tests/ui.md b/src/doc/rustc-dev-guide/src/tests/ui.md
index 25dd5814cf6..d3a2c406402 100644
--- a/src/doc/rustc-dev-guide/src/tests/ui.md
+++ b/src/doc/rustc-dev-guide/src/tests/ui.md
@@ -95,6 +95,7 @@ will check for output files:
   [Normalization](#normalization)).
 - `dont-check-compiler-stderr` — Ignores stderr from the compiler.
 - `dont-check-compiler-stdout` — Ignores stdout from the compiler.
+- `compare-output-by-lines` — Some tests have non-deterministic orders of output, so we need to compare by lines.
 
 UI tests run with `-Zdeduplicate-diagnostics=no` flag which disables rustc's
 built-in diagnostic deduplication mechanism. This means you may see some
diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md
index 07eafdf4c4c..445b10188e3 100644
--- a/src/doc/rustc/src/codegen-options/index.md
+++ b/src/doc/rustc/src/codegen-options/index.md
@@ -375,12 +375,12 @@ linking time. It takes one of the following values:
 
 * `y`, `yes`, `on`, `true`, `fat`, or no value: perform "fat" LTO which attempts to
   perform optimizations across all crates within the dependency graph.
-* `n`, `no`, `off`, `false`: disables LTO.
 * `thin`: perform ["thin"
   LTO](http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html).
   This is similar to "fat", but takes substantially less time to run while
   still achieving performance gains similar to "fat".
   For larger projects like the Rust compiler, ThinLTO can even result in better performance than fat LTO.
+* `n`, `no`, `off`, `false`: disables LTO.
 
 If `-C lto` is not specified, then the compiler will attempt to perform "thin
 local LTO" which performs "thin" LTO on the local crate only across its
diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md
index 89b43cda9b9..3bf87994297 100644
--- a/src/doc/rustc/src/platform-support.md
+++ b/src/doc/rustc/src/platform-support.md
@@ -36,7 +36,6 @@ target | notes
 `aarch64-unknown-linux-gnu` | ARM64 Linux (kernel 4.1+, glibc 2.17+)
 [`i686-pc-windows-msvc`](platform-support/windows-msvc.md) | 32-bit MSVC (Windows 10+, Windows Server 2016+, Pentium 4) [^x86_32-floats-return-ABI] [^win32-msvc-alignment]
 `i686-unknown-linux-gnu` | 32-bit Linux (kernel 3.2+, glibc 2.17+, Pentium 4) [^x86_32-floats-return-ABI]
-[`x86_64-apple-darwin`](platform-support/apple-darwin.md) | 64-bit macOS (10.12+, Sierra+)
 [`x86_64-pc-windows-gnu`](platform-support/windows-gnu.md) | 64-bit MinGW (Windows 10+, Windows Server 2016+)
 [`x86_64-pc-windows-msvc`](platform-support/windows-msvc.md) | 64-bit MSVC (Windows 10+, Windows Server 2016+)
 `x86_64-unknown-linux-gnu` | 64-bit Linux (kernel 3.2+, glibc 2.17+)
@@ -106,6 +105,7 @@ target | notes
 [`riscv64gc-unknown-linux-gnu`](platform-support/riscv64gc-unknown-linux-gnu.md) | RISC-V Linux (kernel 4.20+, glibc 2.29)
 [`riscv64gc-unknown-linux-musl`](platform-support/riscv64gc-unknown-linux-musl.md) | RISC-V Linux (kernel 4.20+, musl 1.2.3)
 [`s390x-unknown-linux-gnu`](platform-support/s390x-unknown-linux-gnu.md) | S390x Linux (kernel 3.2+, glibc 2.17)
+[`x86_64-apple-darwin`](platform-support/apple-darwin.md) | 64-bit macOS (10.12+, Sierra+)
 [`x86_64-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | 64-bit x86 MinGW (Windows 10+), LLVM ABI
 [`x86_64-unknown-freebsd`](platform-support/freebsd.md) | 64-bit x86 FreeBSD
 [`x86_64-unknown-illumos`](platform-support/illumos.md) | illumos
@@ -270,6 +270,7 @@ target | std | host | notes
 [`aarch64-unknown-trusty`](platform-support/trusty.md) | ✓ |  |
 [`aarch64-uwp-windows-msvc`](platform-support/uwp-windows-msvc.md) | ✓ |  |
 [`aarch64-wrs-vxworks`](platform-support/vxworks.md) | ✓ |  | ARM64 VxWorks OS
+[`aarch64_be-unknown-hermit`](platform-support/hermit.md) | ✓ |  | ARM64 Hermit (big-endian)
 `aarch64_be-unknown-linux-gnu` | ✓ | ✓ | ARM64 Linux (big-endian)
 `aarch64_be-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (big-endian, ILP32 ABI)
 [`aarch64_be-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | ARM64 NetBSD (big-endian)
diff --git a/src/doc/rustc/src/platform-support/apple-darwin.md b/src/doc/rustc/src/platform-support/apple-darwin.md
index e41aee9bdb2..bdbb3a663f1 100644
--- a/src/doc/rustc/src/platform-support/apple-darwin.md
+++ b/src/doc/rustc/src/platform-support/apple-darwin.md
@@ -4,9 +4,12 @@ Apple macOS targets.
 
 **Tier: 1**
 
-- `x86_64-apple-darwin`: macOS on 64-bit x86.
 - `aarch64-apple-darwin`: macOS on ARM64 (M1-family or later Apple Silicon CPUs).
 
+**Tier: 2**
+
+- `x86_64-apple-darwin`: macOS on 64-bit x86.
+
 ## Target maintainers
 
 [@thomcc](https://github.com/thomcc)
diff --git a/src/doc/rustc/src/platform-support/hermit.md b/src/doc/rustc/src/platform-support/hermit.md
index 069c253bd38..8362d6f55fd 100644
--- a/src/doc/rustc/src/platform-support/hermit.md
+++ b/src/doc/rustc/src/platform-support/hermit.md
@@ -10,6 +10,7 @@ Target triplets available so far:
 
 - `x86_64-unknown-hermit`
 - `aarch64-unknown-hermit`
+- `aarch64_be-unknown-hermit`
 - `riscv64gc-unknown-hermit`
 
 ## Target maintainers
@@ -42,6 +43,7 @@ target = [
     "<HOST_TARGET>",
     "x86_64-unknown-hermit",
     "aarch64-unknown-hermit",
+    "aarch64_be-unknown-hermit",
     "riscv64gc-unknown-hermit",
 ]
 
diff --git a/src/doc/rustc/src/platform-support/openharmony.md b/src/doc/rustc/src/platform-support/openharmony.md
index 3acdc3707a8..de3b83d6c2c 100644
--- a/src/doc/rustc/src/platform-support/openharmony.md
+++ b/src/doc/rustc/src/platform-support/openharmony.md
@@ -16,7 +16,7 @@ system.
 ## Target maintainers
 
 [@Amanieu](https://github.com/Amanieu)
-[@lubinglun](https://github.com/lubinglun)
+[@cceerczw](https://github.com/cceerczw)
 
 ## Requirements
 
diff --git a/src/doc/rustc/src/platform-support/wasm32-wali-linux.md b/src/doc/rustc/src/platform-support/wasm32-wali-linux.md
index 3213e2b0c8f..001159b0d32 100644
--- a/src/doc/rustc/src/platform-support/wasm32-wali-linux.md
+++ b/src/doc/rustc/src/platform-support/wasm32-wali-linux.md
@@ -31,7 +31,7 @@ This target is cross-compiled and requires an installation of the [WALI compiler
 > **Note**: Users can expect that new enabled-by-default Wasm features for LLVM are transitively incorporatable into this target -- see [wasm32-unknown-unknown](wasm32-unknown-unknown.md) for detailed information on WebAssembly features.
 
 
-> **Note**: The WALI ABI is similar to default Clang wasm32 ABIs but *not identical*. The primary difference is 64-bit `long` types as opposed to 32-bit for wasm32. This is required to mantain minimum source code changes for 64-bit host platforms currently supported. This may change in the future as the spec evolves.
+> **Note**: The WALI ABI is similar to default Clang wasm32 ABIs but *not identical*. The primary difference is 64-bit `long` types as opposed to 32-bit for wasm32. This is required to maintain minimum source code changes for 64-bit host platforms currently supported. This may change in the future as the spec evolves.
 
 ### Execution
 Running generated WALI binaries also requires a supported compliant engine implementation -- a working implementation in the [WebAssembly Micro-Runtime (WAMR)](https://github.com/arjunr2/WALI) is included in the repo.
diff --git a/src/doc/rustc/src/target-tier-policy.md b/src/doc/rustc/src/target-tier-policy.md
index a0acfdf0e4a..28d3dc32a63 100644
--- a/src/doc/rustc/src/target-tier-policy.md
+++ b/src/doc/rustc/src/target-tier-policy.md
@@ -534,10 +534,10 @@ tests, and will reject patches that fail to build or pass the testsuite on a
 target. We hold tier 1 targets to our highest standard of requirements.
 
 A proposed new tier 1 target must be reviewed and approved by the compiler team
-based on these requirements. In addition, the release team must approve the
-viability and value of supporting the target. For a tier 1 target, this will
+based on these requirements. In addition, the infra team must approve the
+viability of supporting the target. For a tier 1 target, this will
 typically take place via a full RFC proposing the target, to be jointly
-reviewed and approved by the compiler team and release team.
+reviewed and approved by the compiler team and infra team.
 
 In addition, the infrastructure team must approve the integration of the target
 into Continuous Integration (CI), and the tier 1 CI-related requirements. This
@@ -617,7 +617,7 @@ including the infrastructure team in the RFC proposing the target.
 A tier 1 target may be demoted if it no longer meets these requirements but
 still meets the requirements for a lower tier. Any proposal for demotion of a
 tier 1 target requires a full RFC process, with approval by the compiler and
-release teams. Any such proposal will be communicated widely to the Rust
+infra teams. Any such proposal will be communicated widely to the Rust
 community, both when initially proposed and before being dropped from a stable
 release. A tier 1 target is highly unlikely to be directly removed without
 first being demoted to tier 2 or tier 3. (The amount of time between such
@@ -628,7 +628,7 @@ planned and scheduled action.)
 
 Raising the baseline expectations of a tier 1 target (such as the minimum CPU
 features or OS version required) requires the approval of the compiler and
-release teams, and should be widely communicated as well, but does not
+infra teams, and should be widely communicated as well, but does not
 necessarily require a full RFC.
 
 ### Tier 1 with host tools
@@ -638,11 +638,11 @@ host (such as `rustc` and `cargo`). This allows the target to be used as a
 development platform, not just a compilation target.
 
 A proposed new tier 1 target with host tools must be reviewed and approved by
-the compiler team based on these requirements. In addition, the release team
-must approve the viability and value of supporting host tools for the target.
+the compiler team based on these requirements. In addition, the infra team
+must approve the viability of supporting host tools for the target.
 For a tier 1 target, this will typically take place via a full RFC proposing
 the target, to be jointly reviewed and approved by the compiler team and
-release team.
+infra team.
 
 In addition, the infrastructure team must approve the integration of the
 target's host tools into Continuous Integration (CI), and the CI-related
@@ -697,7 +697,7 @@ target with host tools may be demoted (including having its host tools dropped,
 or being demoted to tier 2 with host tools) if it no longer meets these
 requirements but still meets the requirements for a lower tier. Any proposal
 for demotion of a tier 1 target (with or without host tools) requires a full
-RFC process, with approval by the compiler and release teams. Any such proposal
+RFC process, with approval by the compiler and infra teams. Any such proposal
 will be communicated widely to the Rust community, both when initially proposed
 and before being dropped from a stable release.
 
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 7bd2970eee7..bb28e3abbf3 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -796,3 +796,7 @@ will be split as follows:
     "you today?",
 ]
 ```
+
+## `--generate-macro-expansion`: Generate macros expansion toggles in source code
+
+This flag enables the generation of toggles to expand macros in the HTML source code pages.
diff --git a/src/doc/unstable-book/src/compiler-flags/indirect-branch-cs-prefix.md b/src/doc/unstable-book/src/compiler-flags/indirect-branch-cs-prefix.md
new file mode 100644
index 00000000000..040e2d41701
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-flags/indirect-branch-cs-prefix.md
@@ -0,0 +1,19 @@
+# `indirect-branch-cs-prefix`
+
+The tracking issue for this feature is: https://github.com/rust-lang/rust/issues/116852.
+
+------------------------
+
+Option `-Zindirect-branch-cs-prefix` controls whether a `cs` prefix is added to
+`call` and `jmp` to indirect thunks.
+
+It is equivalent to [Clang]'s and [GCC]'s `-mindirect-branch-cs-prefix`. The
+Linux kernel uses it for RETPOLINE builds. For details, see
+[LLVM commit 6f867f910283] ("[X86] Support ``-mindirect-branch-cs-prefix`` for
+call and jmp to indirect thunk") which introduces the feature.
+
+Only x86 and x86_64 are supported.
+
+[Clang]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mindirect-branch-cs-prefix
+[GCC]: https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html#index-mindirect-branch-cs-prefix
+[LLVM commit 6f867f910283]: https://github.com/llvm/llvm-project/commit/6f867f9102838ebe314c1f3661fdf95700386e5a
diff --git a/src/doc/unstable-book/src/compiler-flags/randomize-layout.md b/src/doc/unstable-book/src/compiler-flags/randomize-layout.md
index 84c6712bc23..81da0503d7f 100644
--- a/src/doc/unstable-book/src/compiler-flags/randomize-layout.md
+++ b/src/doc/unstable-book/src/compiler-flags/randomize-layout.md
@@ -7,7 +7,7 @@ The tracking issue for this feature is: [#106764](https://github.com/rust-lang/r
 The `-Zrandomize-layout` flag changes the layout algorithm for `repr(Rust)` types defined in the current crate from its normal
 optimization goals to pseudorandomly rearranging fields within the degrees of freedom provided by the largely unspecified
 default representation. This also affects type sizes and padding.
-Downstream intantiations of generic types defined in a crate with randomization enabled will also be randomized.
+Downstream instantiations of generic types defined in a crate with randomization enabled will also be randomized.
 
 It can be used to find unsafe code that accidentally relies on unspecified behavior.
 
diff --git a/src/doc/unstable-book/src/language-features/no-sanitize.md b/src/doc/unstable-book/src/language-features/no-sanitize.md
deleted file mode 100644
index 28c683934d4..00000000000
--- a/src/doc/unstable-book/src/language-features/no-sanitize.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# `no_sanitize`
-
-The tracking issue for this feature is: [#39699]
-
-[#39699]: https://github.com/rust-lang/rust/issues/39699
-
-------------------------
-
-The `no_sanitize` attribute can be used to selectively disable sanitizer
-instrumentation in an annotated function. This might be useful to: avoid
-instrumentation overhead in a performance critical function, or avoid
-instrumenting code that contains constructs unsupported by given sanitizer.
-
-The precise effect of this annotation depends on particular sanitizer in use.
-For example, with `no_sanitize(thread)`, the thread sanitizer will no longer
-instrument non-atomic store / load operations, but it will instrument atomic
-operations to avoid reporting false positives and provide meaning full stack
-traces.
-
-## Examples
-
-``` rust
-#![feature(no_sanitize)]
-
-#[no_sanitize(address)]
-fn foo() {
-  // ...
-}
-```
diff --git a/src/doc/unstable-book/src/language-features/sanitize.md b/src/doc/unstable-book/src/language-features/sanitize.md
new file mode 100644
index 00000000000..fcd099cb36e
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/sanitize.md
@@ -0,0 +1,73 @@
+# `sanitize`
+
+The tracking issue for this feature is: [#39699]
+
+[#39699]: https://github.com/rust-lang/rust/issues/39699
+
+------------------------
+
+The `sanitize` attribute can be used to selectively disable or enable sanitizer
+instrumentation in an annotated function. This might be useful to: avoid
+instrumentation overhead in a performance critical function, or avoid
+instrumenting code that contains constructs unsupported by given sanitizer.
+
+The precise effect of this annotation depends on particular sanitizer in use.
+For example, with `sanitize(thread = "off")`, the thread sanitizer will no
+longer instrument non-atomic store / load operations, but it will instrument
+atomic operations to avoid reporting false positives and provide meaning full
+stack traces.
+
+This attribute was previously named `no_sanitize`.
+
+## Examples
+
+``` rust
+#![feature(sanitize)]
+
+#[sanitize(address = "off")]
+fn foo() {
+  // ...
+}
+```
+
+It is also possible to disable sanitizers for entire modules and enable them
+for single items or functions.
+
+```rust
+#![feature(sanitize)]
+
+#[sanitize(address = "off")]
+mod foo {
+  fn unsanitized() {
+    // ...
+  }
+
+  #[sanitize(address = "on")]
+  fn sanitized() {
+    // ...
+  }
+}
+```
+
+It's also applicable to impl blocks.
+
+```rust
+#![feature(sanitize)]
+
+trait MyTrait {
+  fn foo(&self);
+  fn bar(&self);
+}
+
+#[sanitize(address = "off")]
+impl MyTrait for () {
+  fn foo(&self) {
+    // ...
+  }
+
+  #[sanitize(address = "on")]
+  fn bar(&self) {
+    // ...
+  }
+}
+```
diff --git a/src/etc/completions/x.fish b/src/etc/completions/x.fish
index 0b9af334214..544f9b97237 100644
--- a/src/etc/completions/x.fish
+++ b/src/etc/completions/x.fish
@@ -305,6 +305,7 @@ complete -c x -n "__fish_x_using_subcommand test" -l extra-checks -d 'comma-sepa
 complete -c x -n "__fish_x_using_subcommand test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r
 complete -c x -n "__fish_x_using_subcommand test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r
 complete -c x -n "__fish_x_using_subcommand test" -l run -d 'whether to execute run-* tests' -r
+complete -c x -n "__fish_x_using_subcommand test" -l test-codegen-backend -d 'Use a different codegen backend when running tests' -r
 complete -c x -n "__fish_x_using_subcommand test" -l config -d 'TOML configuration file for build' -r -F
 complete -c x -n "__fish_x_using_subcommand test" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)"
 complete -c x -n "__fish_x_using_subcommand test" -l build -d 'host target of the stage0 compiler' -r -f
diff --git a/src/etc/completions/x.ps1 b/src/etc/completions/x.ps1
index 95cee4b6336..b03acf930f7 100644
--- a/src/etc/completions/x.ps1
+++ b/src/etc/completions/x.ps1
@@ -351,6 +351,7 @@ Register-ArgumentCompleter -Native -CommandName 'x' -ScriptBlock {
             [CompletionResult]::new('--compare-mode', '--compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to')
             [CompletionResult]::new('--pass', '--pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode')
             [CompletionResult]::new('--run', '--run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests')
+            [CompletionResult]::new('--test-codegen-backend', '--test-codegen-backend', [CompletionResultType]::ParameterName, 'Use a different codegen backend when running tests')
             [CompletionResult]::new('--config', '--config', [CompletionResultType]::ParameterName, 'TOML configuration file for build')
             [CompletionResult]::new('--build-dir', '--build-dir', [CompletionResultType]::ParameterName, 'Build directory, overrides `build.build-dir` in `bootstrap.toml`')
             [CompletionResult]::new('--build', '--build', [CompletionResultType]::ParameterName, 'host target of the stage0 compiler')
diff --git a/src/etc/completions/x.py.fish b/src/etc/completions/x.py.fish
index 6fba6a45623..08e4cd26ce8 100644
--- a/src/etc/completions/x.py.fish
+++ b/src/etc/completions/x.py.fish
@@ -305,6 +305,7 @@ complete -c x.py -n "__fish_x.py_using_subcommand test" -l extra-checks -d 'comm
 complete -c x.py -n "__fish_x.py_using_subcommand test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r
 complete -c x.py -n "__fish_x.py_using_subcommand test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r
 complete -c x.py -n "__fish_x.py_using_subcommand test" -l run -d 'whether to execute run-* tests' -r
+complete -c x.py -n "__fish_x.py_using_subcommand test" -l test-codegen-backend -d 'Use a different codegen backend when running tests' -r
 complete -c x.py -n "__fish_x.py_using_subcommand test" -l config -d 'TOML configuration file for build' -r -F
 complete -c x.py -n "__fish_x.py_using_subcommand test" -l build-dir -d 'Build directory, overrides `build.build-dir` in `bootstrap.toml`' -r -f -a "(__fish_complete_directories)"
 complete -c x.py -n "__fish_x.py_using_subcommand test" -l build -d 'host target of the stage0 compiler' -r -f
diff --git a/src/etc/completions/x.py.ps1 b/src/etc/completions/x.py.ps1
index 458879a17a7..3d95d88af49 100644
--- a/src/etc/completions/x.py.ps1
+++ b/src/etc/completions/x.py.ps1
@@ -351,6 +351,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock {
             [CompletionResult]::new('--compare-mode', '--compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to')
             [CompletionResult]::new('--pass', '--pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode')
             [CompletionResult]::new('--run', '--run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests')
+            [CompletionResult]::new('--test-codegen-backend', '--test-codegen-backend', [CompletionResultType]::ParameterName, 'Use a different codegen backend when running tests')
             [CompletionResult]::new('--config', '--config', [CompletionResultType]::ParameterName, 'TOML configuration file for build')
             [CompletionResult]::new('--build-dir', '--build-dir', [CompletionResultType]::ParameterName, 'Build directory, overrides `build.build-dir` in `bootstrap.toml`')
             [CompletionResult]::new('--build', '--build', [CompletionResultType]::ParameterName, 'host target of the stage0 compiler')
diff --git a/src/etc/completions/x.py.sh b/src/etc/completions/x.py.sh
index e003bf7fd0b..8ff0eaf35c8 100644
--- a/src/etc/completions/x.py.sh
+++ b/src/etc/completions/x.py.sh
@@ -3875,7 +3875,7 @@ _x.py() {
             return 0
             ;;
         x.py__test)
-            opts="-v -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --no-doc --doc --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..."
+            opts="-v -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --no-doc --doc --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --test-codegen-backend --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..."
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
@@ -3905,6 +3905,10 @@ _x.py() {
                     COMPREPLY=($(compgen -f "${cur}"))
                     return 0
                     ;;
+                --test-codegen-backend)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
                 --config)
                     local oldifs
                     if [ -n "${IFS+x}" ]; then
diff --git a/src/etc/completions/x.py.zsh b/src/etc/completions/x.py.zsh
index b82c2d65e86..9d2d73e582e 100644
--- a/src/etc/completions/x.py.zsh
+++ b/src/etc/completions/x.py.zsh
@@ -351,6 +351,7 @@ _arguments "${_arguments_options[@]}" : \
 '--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE:_default' \
 '--pass=[force {check,build,run}-pass tests to this mode]:check | build | run:_default' \
 '--run=[whether to execute run-* tests]:auto | always | never:_default' \
+'--test-codegen-backend=[Use a different codegen backend when running tests]:TEST_CODEGEN_BACKEND:_default' \
 '--config=[TOML configuration file for build]:FILE:_files' \
 '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \
 '--build=[host target of the stage0 compiler]:BUILD:' \
diff --git a/src/etc/completions/x.sh b/src/etc/completions/x.sh
index c2cb7710020..c1b73fb7c9e 100644
--- a/src/etc/completions/x.sh
+++ b/src/etc/completions/x.sh
@@ -3875,7 +3875,7 @@ _x() {
             return 0
             ;;
         x__test)
-            opts="-v -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --no-doc --doc --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..."
+            opts="-v -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --no-doc --doc --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --test-codegen-backend --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..."
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
@@ -3905,6 +3905,10 @@ _x() {
                     COMPREPLY=($(compgen -f "${cur}"))
                     return 0
                     ;;
+                --test-codegen-backend)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
                 --config)
                     local oldifs
                     if [ -n "${IFS+x}" ]; then
diff --git a/src/etc/completions/x.zsh b/src/etc/completions/x.zsh
index 49139e70f7f..29237ef9bf8 100644
--- a/src/etc/completions/x.zsh
+++ b/src/etc/completions/x.zsh
@@ -351,6 +351,7 @@ _arguments "${_arguments_options[@]}" : \
 '--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE:_default' \
 '--pass=[force {check,build,run}-pass tests to this mode]:check | build | run:_default' \
 '--run=[whether to execute run-* tests]:auto | always | never:_default' \
+'--test-codegen-backend=[Use a different codegen backend when running tests]:TEST_CODEGEN_BACKEND:_default' \
 '--config=[TOML configuration file for build]:FILE:_files' \
 '--build-dir=[Build directory, overrides \`build.build-dir\` in \`bootstrap.toml\`]:DIR:_files -/' \
 '--build=[host target of the stage0 compiler]:BUILD:' \
diff --git a/src/etc/htmldocck.py b/src/etc/htmldocck.py
index 72975cc6206..8d7f7341c2e 100755
--- a/src/etc/htmldocck.py
+++ b/src/etc/htmldocck.py
@@ -15,6 +15,7 @@ import os.path
 import re
 import shlex
 from collections import namedtuple
+from pathlib import Path
 
 try:
     from html.parser import HTMLParser
@@ -242,6 +243,11 @@ class CachedFiles(object):
             return self.last_path
 
     def get_absolute_path(self, path):
+        if "*" in path:
+            paths = list(Path(self.root).glob(path))
+            if len(paths) != 1:
+                raise FailedCheck("glob path does not resolve to one file")
+            path = str(paths[0])
         return os.path.join(self.root, path)
 
     def get_file(self, path):
diff --git a/src/etc/lldb_lookup.py b/src/etc/lldb_lookup.py
index f4ea904b7f5..f43d2c6a725 100644
--- a/src/etc/lldb_lookup.py
+++ b/src/etc/lldb_lookup.py
@@ -10,6 +10,9 @@ def is_hashbrown_hashmap(hash_map: lldb.SBValue) -> bool:
 
 
 def classify_rust_type(type: lldb.SBType) -> str:
+    if type.IsPointerType():
+        type = type.GetPointeeType()
+
     type_class = type.GetTypeClass()
     if type_class == lldb.eTypeClassStruct:
         return classify_struct(type.name, type.fields)
diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py
index 98426e42423..65f18baa937 100644
--- a/src/etc/lldb_providers.py
+++ b/src/etc/lldb_providers.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+import re
 import sys
 from typing import List, TYPE_CHECKING
 
@@ -410,6 +411,16 @@ class MSVCStrSyntheticProvider:
             return "&str"
 
 
+def _getVariantName(variant) -> str:
+    """
+    Since the enum variant's type name is in the form `TheEnumName::TheVariantName$Variant`,
+    we can extract `TheVariantName` from it for display purpose.
+    """
+    s = variant.GetType().GetName()
+    match = re.search(r"::([^:]+)\$Variant$", s)
+    return match.group(1) if match else ""
+
+
 class ClangEncodedEnumProvider:
     """Pretty-printer for 'clang-encoded' enums support implemented in LLDB"""
 
@@ -424,37 +435,25 @@ class ClangEncodedEnumProvider:
         return True
 
     def num_children(self) -> int:
-        if self.is_default:
-            return 1
-        return 2
+        return 1
 
-    def get_child_index(self, name: str) -> int:
-        if name == ClangEncodedEnumProvider.VALUE_MEMBER_NAME:
-            return 0
-        if name == ClangEncodedEnumProvider.DISCRIMINANT_MEMBER_NAME:
-            return 1
+    def get_child_index(self, _name: str) -> int:
         return -1
 
     def get_child_at_index(self, index: int) -> SBValue:
         if index == 0:
-            return self.variant.GetChildMemberWithName(
+            value = self.variant.GetChildMemberWithName(
                 ClangEncodedEnumProvider.VALUE_MEMBER_NAME
             )
-        if index == 1:
-            return self.variant.GetChildMemberWithName(
-                ClangEncodedEnumProvider.DISCRIMINANT_MEMBER_NAME
+            return value.CreateChildAtOffset(
+                _getVariantName(self.variant), 0, value.GetType()
             )
+        return None
 
     def update(self):
         all_variants = self.valobj.GetChildAtIndex(0)
         index = self._getCurrentVariantIndex(all_variants)
         self.variant = all_variants.GetChildAtIndex(index)
-        self.is_default = (
-            self.variant.GetIndexOfChildWithName(
-                ClangEncodedEnumProvider.DISCRIMINANT_MEMBER_NAME
-            )
-            == -1
-        )
 
     def _getCurrentVariantIndex(self, all_variants: SBValue) -> int:
         default_index = 0
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml
index fdde8309cf9..5d36ffc2d3a 100644
--- a/src/librustdoc/Cargo.toml
+++ b/src/librustdoc/Cargo.toml
@@ -21,6 +21,7 @@ rustdoc-json-types = { path = "../rustdoc-json-types" }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 smallvec = "1.8.1"
+stringdex = { version = "0.0.1-alpha4" }
 tempfile = "3"
 threadpool = "1.8.1"
 tracing = "0.1"
diff --git a/src/librustdoc/build.rs b/src/librustdoc/build.rs
index 07269d5bdc2..5b497183ae6 100644
--- a/src/librustdoc/build.rs
+++ b/src/librustdoc/build.rs
@@ -10,6 +10,7 @@ fn main() {
         "static/css/normalize.css",
         "static/js/main.js",
         "static/js/search.js",
+        "static/js/stringdex.js",
         "static/js/settings.js",
         "static/js/src-script.js",
         "static/js/storage.js",
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 26b087feb16..92bd4a498ca 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -1087,7 +1087,8 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
 
     // treat #[target_feature(enable = "feat")] attributes as if they were
     // #[doc(cfg(target_feature = "feat"))] attributes as well
-    if let Some(features) = find_attr!(attrs, AttributeKind::TargetFeature(features, _) => features)
+    if let Some(features) =
+        find_attr!(attrs, AttributeKind::TargetFeature { features, .. } => features)
     {
         for (feature, _) in features {
             cfg &= Cfg::Cfg(sym::target_feature, Some(*feature));
@@ -1552,10 +1553,10 @@ impl Type {
         matches!(self, Type::Path { path: Path { res: Res::Def(DefKind::TyAlias, _), .. } })
     }
 
-    /// Check if two types are "the same" for documentation purposes.
+    /// Check if this type is a subtype of another type for documentation purposes.
     ///
     /// This is different from `Eq`, because it knows that things like
-    /// `Placeholder` are possible matches for everything.
+    /// `Infer` and generics have special subtyping rules.
     ///
     /// This relation is not commutative when generics are involved:
     ///
@@ -1566,8 +1567,8 @@ impl Type {
     /// let cache = Cache::new(false);
     /// let generic = Type::Generic(rustc_span::symbol::sym::Any);
     /// let unit = Type::Primitive(PrimitiveType::Unit);
-    /// assert!(!generic.is_same(&unit, &cache));
-    /// assert!(unit.is_same(&generic, &cache));
+    /// assert!(!generic.is_doc_subtype_of(&unit, &cache));
+    /// assert!(unit.is_doc_subtype_of(&generic, &cache));
     /// ```
     ///
     /// An owned type is also the same as its borrowed variants (this is commutative),
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index c52c7236883..450ac04b40d 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -305,6 +305,8 @@ pub(crate) struct RenderOptions {
     pub(crate) parts_out_dir: Option<PathToParts>,
     /// disable minification of CSS/JS
     pub(crate) disable_minification: bool,
+    /// If `true`, HTML source pages will generate the possibility to expand macros.
+    pub(crate) generate_macro_expansion: bool,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -786,6 +788,7 @@ impl Options {
         let show_type_layout = matches.opt_present("show-type-layout");
         let nocapture = matches.opt_present("nocapture");
         let generate_link_to_definition = matches.opt_present("generate-link-to-definition");
+        let generate_macro_expansion = matches.opt_present("generate-macro-expansion");
         let extern_html_root_takes_precedence =
             matches.opt_present("extern-html-root-takes-precedence");
         let html_no_source = matches.opt_present("html-no-source");
@@ -801,6 +804,13 @@ impl Options {
             .with_note("`--generate-link-to-definition` option will be ignored")
             .emit();
         }
+        if generate_macro_expansion && (show_coverage || output_format != OutputFormat::Html) {
+            dcx.struct_warn(
+                "`--generate-macro-expansion` option can only be used with HTML output format",
+            )
+            .with_note("`--generate-macro-expansion` option will be ignored")
+            .emit();
+        }
 
         let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx);
         let with_examples = matches.opt_strs("with-examples");
@@ -881,6 +891,7 @@ impl Options {
             unstable_features,
             emit,
             generate_link_to_definition,
+            generate_macro_expansion,
             call_locations,
             no_emit_shared: false,
             html_no_source,
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index e89733b2f6d..b8aaafcb517 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -31,6 +31,7 @@ use crate::clean::inline::build_trait;
 use crate::clean::{self, ItemId};
 use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
 use crate::formats::cache::Cache;
+use crate::html::macro_expansion::{ExpandedCode, source_macro_expansion};
 use crate::passes;
 use crate::passes::Condition::*;
 use crate::passes::collect_intra_doc_links::LinkCollector;
@@ -334,11 +335,19 @@ pub(crate) fn run_global_ctxt(
     show_coverage: bool,
     render_options: RenderOptions,
     output_format: OutputFormat,
-) -> (clean::Crate, RenderOptions, Cache) {
+) -> (clean::Crate, RenderOptions, Cache, FxHashMap<rustc_span::BytePos, Vec<ExpandedCode>>) {
     // Certain queries assume that some checks were run elsewhere
     // (see https://github.com/rust-lang/rust/pull/73566#issuecomment-656954425),
     // so type-check everything other than function bodies in this crate before running lints.
 
+    let expanded_macros = {
+        // We need for these variables to be removed to ensure that the `Crate` won't be "stolen"
+        // anymore.
+        let (_resolver, krate) = &*tcx.resolver_for_lowering().borrow();
+
+        source_macro_expansion(&krate, &render_options, output_format, tcx.sess.source_map())
+    };
+
     // NOTE: this does not call `tcx.analysis()` so that we won't
     // typeck function bodies or run the default rustc lints.
     // (see `override_queries` in the `config`)
@@ -448,7 +457,7 @@ pub(crate) fn run_global_ctxt(
 
     tcx.dcx().abort_if_errors();
 
-    (krate, ctxt.render_options, ctxt.cache)
+    (krate, ctxt.render_options, ctxt.cache, expanded_macros)
 }
 
 /// Due to <https://github.com/rust-lang/rust/pull/73566>,
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index 80399cf3842..cb6837dd614 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -1,6 +1,5 @@
 use std::mem;
 
-use rustc_ast::join_path_syms;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
 use rustc_hir::StabilityLevel;
 use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet};
@@ -48,7 +47,7 @@ pub(crate) struct Cache {
 
     /// Similar to `paths`, but only holds external paths. This is only used for
     /// generating explicit hyperlinks to other crates.
-    pub(crate) external_paths: FxHashMap<DefId, (Vec<Symbol>, ItemType)>,
+    pub(crate) external_paths: FxIndexMap<DefId, (Vec<Symbol>, ItemType)>,
 
     /// Maps local `DefId`s of exported types to fully qualified paths.
     /// Unlike 'paths', this mapping ignores any renames that occur
@@ -574,7 +573,6 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It
         clean::ItemKind::ImportItem(import) => import.source.did.unwrap_or(item_def_id),
         _ => item_def_id,
     };
-    let path = join_path_syms(parent_path);
     let impl_id = if let Some(ParentStackItem::Impl { item_id, .. }) = cache.parent_stack.last() {
         item_id.as_def_id()
     } else {
@@ -593,11 +591,11 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It
         ty: item.type_(),
         defid: Some(defid),
         name,
-        path,
+        module_path: parent_path.to_vec(),
         desc,
         parent: parent_did,
         parent_idx: None,
-        exact_path: None,
+        exact_module_path: None,
         impl_id,
         search_type,
         aliases,
diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs
index 1dba84aa44c..142a9d7d8af 100644
--- a/src/librustdoc/formats/item_type.rs
+++ b/src/librustdoc/formats/item_type.rs
@@ -4,7 +4,7 @@ use std::fmt;
 
 use rustc_hir::def::{CtorOf, DefKind, MacroKinds};
 use rustc_span::hygiene::MacroKind;
-use serde::{Serialize, Serializer};
+use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
 
 use crate::clean;
 
@@ -68,6 +68,52 @@ impl Serialize for ItemType {
     }
 }
 
+impl<'de> Deserialize<'de> for ItemType {
+    fn deserialize<D>(deserializer: D) -> Result<ItemType, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct ItemTypeVisitor;
+        impl<'de> de::Visitor<'de> for ItemTypeVisitor {
+            type Value = ItemType;
+            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(formatter, "an integer between 0 and 25")
+            }
+            fn visit_u64<E: de::Error>(self, v: u64) -> Result<ItemType, E> {
+                Ok(match v {
+                    0 => ItemType::Keyword,
+                    1 => ItemType::Primitive,
+                    2 => ItemType::Module,
+                    3 => ItemType::ExternCrate,
+                    4 => ItemType::Import,
+                    5 => ItemType::Struct,
+                    6 => ItemType::Enum,
+                    7 => ItemType::Function,
+                    8 => ItemType::TypeAlias,
+                    9 => ItemType::Static,
+                    10 => ItemType::Trait,
+                    11 => ItemType::Impl,
+                    12 => ItemType::TyMethod,
+                    13 => ItemType::Method,
+                    14 => ItemType::StructField,
+                    15 => ItemType::Variant,
+                    16 => ItemType::Macro,
+                    17 => ItemType::AssocType,
+                    18 => ItemType::Constant,
+                    19 => ItemType::AssocConst,
+                    20 => ItemType::Union,
+                    21 => ItemType::ForeignType,
+                    23 => ItemType::ProcAttribute,
+                    24 => ItemType::ProcDerive,
+                    25 => ItemType::TraitAlias,
+                    _ => return Err(E::missing_field("unknown number")),
+                })
+            }
+        }
+        deserializer.deserialize_any(ItemTypeVisitor)
+    }
+}
+
 impl<'a> From<&'a clean::Item> for ItemType {
     fn from(item: &'a clean::Item) -> ItemType {
         let kind = match &item.kind {
@@ -198,6 +244,10 @@ impl ItemType {
     pub(crate) fn is_adt(&self) -> bool {
         matches!(self, ItemType::Struct | ItemType::Union | ItemType::Enum)
     }
+    /// Keep this the same as isFnLikeTy in search.js
+    pub(crate) fn is_fn_like(&self) -> bool {
+        matches!(self, ItemType::Function | ItemType::Method | ItemType::TyMethod)
+    }
 }
 
 impl fmt::Display for ItemType {
diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs
index aa4be4db997..305c8c39ba7 100644
--- a/src/librustdoc/formats/renderer.rs
+++ b/src/librustdoc/formats/renderer.rs
@@ -31,15 +31,6 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
     /// reset the information between each call to `item` by using `restore_module_data`.
     type ModuleData;
 
-    /// Sets up any state required for the renderer. When this is called the cache has already been
-    /// populated.
-    fn init(
-        krate: clean::Crate,
-        options: RenderOptions,
-        cache: Cache,
-        tcx: TyCtxt<'tcx>,
-    ) -> Result<(Self, clean::Crate), Error>;
-
     /// This method is called right before call [`Self::item`]. This method returns a type
     /// containing information that needs to be reset after the [`Self::item`] method has been
     /// called with the [`Self::restore_module_data`] method.
@@ -105,18 +96,23 @@ fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
 }
 
 /// Main method for rendering a crate.
-pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>(
+pub(crate) fn run_format<
+    'tcx,
+    T: FormatRenderer<'tcx>,
+    F: FnOnce(clean::Crate, RenderOptions, Cache, TyCtxt<'tcx>) -> Result<(T, clean::Crate), Error>,
+>(
     krate: clean::Crate,
     options: RenderOptions,
     cache: Cache,
     tcx: TyCtxt<'tcx>,
+    init: F,
 ) -> Result<(), Error> {
     let prof = &tcx.sess.prof;
 
     let emit_crate = options.should_emit_crate();
     let (mut format_renderer, krate) = prof
         .verbose_generic_activity_with_arg("create_renderer", T::descr())
-        .run(|| T::init(krate, options, cache, tcx))?;
+        .run(|| init(krate, options, cache, tcx))?;
 
     if !emit_crate {
         return Ok(());
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 272180fb990..feafb41dc99 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -5,6 +5,7 @@
 //!
 //! Use the `render_with_highlighting` to highlight some rust code.
 
+use std::borrow::Cow;
 use std::collections::VecDeque;
 use std::fmt::{self, Display, Write};
 
@@ -17,6 +18,7 @@ use rustc_span::{BytePos, DUMMY_SP, Span};
 use super::format::{self, write_str};
 use crate::clean::PrimitiveType;
 use crate::html::escape::EscapeBodyText;
+use crate::html::macro_expansion::ExpandedCode;
 use crate::html::render::{Context, LinkFromSrc};
 
 /// This type is needed in case we want to render links on items to allow to go to their definition.
@@ -163,11 +165,22 @@ struct TokenHandler<'a, 'tcx, F: Write> {
     current_class: Option<Class>,
     /// We need to keep the `Class` for each element because it could contain a `Span` which is
     /// used to generate links.
-    pending_elems: Vec<(&'a str, Option<Class>)>,
+    pending_elems: Vec<(Cow<'a, str>, Option<Class>)>,
     href_context: Option<HrefContext<'a, 'tcx>>,
     write_line_number: fn(&mut F, u32, &'static str),
 }
 
+impl<F: Write> std::fmt::Debug for TokenHandler<'_, '_, F> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("TokenHandler")
+            .field("closing_tags", &self.closing_tags)
+            .field("pending_exit_span", &self.pending_exit_span)
+            .field("current_class", &self.current_class)
+            .field("pending_elems", &self.pending_elems)
+            .finish()
+    }
+}
+
 impl<F: Write> TokenHandler<'_, '_, F> {
     fn handle_exit_span(&mut self) {
         // We can't get the last `closing_tags` element using `pop()` because `closing_tags` is
@@ -220,6 +233,10 @@ impl<F: Write> TokenHandler<'_, '_, F> {
             } else {
                 None
             };
+            // To prevent opening a macro expansion span being closed right away because
+            // the currently open item is replaced by a new class.
+            let last_pending =
+                self.pending_elems.pop_if(|(_, class)| *class == Some(Class::Expansion));
             for (text, class) in self.pending_elems.iter() {
                 string(
                     self.out,
@@ -233,6 +250,16 @@ impl<F: Write> TokenHandler<'_, '_, F> {
             if let Some(close_tag) = close_tag {
                 exit_span(self.out, close_tag);
             }
+            if let Some((text, class)) = last_pending {
+                string(
+                    self.out,
+                    EscapeBodyText(&text),
+                    class,
+                    &self.href_context,
+                    close_tag.is_none(),
+                    self.write_line_number,
+                );
+            }
         }
         self.pending_elems.clear();
         true
@@ -271,6 +298,100 @@ fn empty_line_number(out: &mut impl Write, _: u32, extra: &'static str) {
     out.write_str(extra).unwrap();
 }
 
+fn get_next_expansion(
+    expanded_codes: &[ExpandedCode],
+    line: u32,
+    span: Span,
+) -> Option<&ExpandedCode> {
+    expanded_codes.iter().find(|code| code.start_line == line && code.span.lo() > span.lo())
+}
+
+fn get_expansion<'a, W: Write>(
+    token_handler: &mut TokenHandler<'_, '_, W>,
+    expanded_codes: &'a [ExpandedCode],
+    line: u32,
+    span: Span,
+) -> Option<&'a ExpandedCode> {
+    if let Some(expanded_code) = get_next_expansion(expanded_codes, line, span) {
+        let (closing, reopening) = if let Some(current_class) = token_handler.current_class
+            && let class = current_class.as_html()
+            && !class.is_empty()
+        {
+            ("</span>", format!("<span class=\"{class}\">"))
+        } else {
+            ("", String::new())
+        };
+        let id = format!("expand-{line}");
+        token_handler.pending_elems.push((
+            Cow::Owned(format!(
+                "{closing}\
+<span class=expansion>\
+    <input id={id} \
+           tabindex=0 \
+           type=checkbox \
+           aria-label=\"Collapse/expand macro\" \
+           title=\"\"Collapse/expand macro\">{reopening}",
+            )),
+            Some(Class::Expansion),
+        ));
+        Some(expanded_code)
+    } else {
+        None
+    }
+}
+
+fn start_expansion(out: &mut Vec<(Cow<'_, str>, Option<Class>)>, expanded_code: &ExpandedCode) {
+    out.push((
+        Cow::Owned(format!(
+            "<span class=expanded>{}</span><span class=original>",
+            expanded_code.code,
+        )),
+        Some(Class::Expansion),
+    ));
+}
+
+fn end_expansion<'a, W: Write>(
+    token_handler: &mut TokenHandler<'_, '_, W>,
+    expanded_codes: &'a [ExpandedCode],
+    expansion_start_tags: &[(&'static str, Class)],
+    line: u32,
+    span: Span,
+) -> Option<&'a ExpandedCode> {
+    if let Some(expanded_code) = get_next_expansion(expanded_codes, line, span) {
+        // We close the current "original" content.
+        token_handler.pending_elems.push((Cow::Borrowed("</span>"), Some(Class::Expansion)));
+        return Some(expanded_code);
+    }
+    if expansion_start_tags.is_empty() && token_handler.closing_tags.is_empty() {
+        // No need tag opened so we can just close expansion.
+        token_handler.pending_elems.push((Cow::Borrowed("</span></span>"), Some(Class::Expansion)));
+        return None;
+    }
+
+    // If tags were opened inside the expansion, we need to close them and re-open them outside
+    // of the expansion span.
+    let mut out = String::new();
+    let mut end = String::new();
+
+    let mut closing_tags = token_handler.closing_tags.iter().peekable();
+    let mut start_closing_tags = expansion_start_tags.iter().peekable();
+
+    while let (Some(tag), Some(start_tag)) = (closing_tags.peek(), start_closing_tags.peek())
+        && tag == start_tag
+    {
+        closing_tags.next();
+        start_closing_tags.next();
+    }
+    for (tag, class) in start_closing_tags.chain(closing_tags) {
+        out.push_str(tag);
+        end.push_str(&format!("<span class=\"{}\">", class.as_html()));
+    }
+    token_handler
+        .pending_elems
+        .push((Cow::Owned(format!("</span></span>{out}{end}")), Some(Class::Expansion)));
+    None
+}
+
 #[derive(Clone, Copy)]
 pub(super) struct LineInfo {
     pub(super) start_line: u32,
@@ -317,7 +438,7 @@ pub(super) fn write_code(
         closing_tags: Vec::new(),
         pending_exit_span: None,
         current_class: None,
-        pending_elems: Vec::new(),
+        pending_elems: Vec::with_capacity(20),
         href_context,
         write_line_number: match line_info {
             Some(line_info) => {
@@ -338,12 +459,23 @@ pub(super) fn write_code(
         (0, u32::MAX)
     };
 
+    let (expanded_codes, file_span) = match token_handler.href_context.as_ref().and_then(|c| {
+        let expanded_codes = c.context.shared.expanded_codes.get(&c.file_span.lo())?;
+        Some((expanded_codes, c.file_span))
+    }) {
+        Some((expanded_codes, file_span)) => (expanded_codes.as_slice(), file_span),
+        None => (&[] as &[ExpandedCode], DUMMY_SP),
+    };
+    let mut current_expansion = get_expansion(&mut token_handler, expanded_codes, line, file_span);
+    token_handler.write_pending_elems(None);
+    let mut expansion_start_tags = Vec::new();
+
     Classifier::new(
         &src,
         token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
         decoration_info,
     )
-    .highlight(&mut |highlight| {
+    .highlight(&mut |span, highlight| {
         match highlight {
             Highlight::Token { text, class } => {
                 // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
@@ -369,10 +501,42 @@ pub(super) fn write_code(
                 if text == "\n" {
                     line += 1;
                     if line < max_lines {
-                        token_handler.pending_elems.push((text, Some(Class::Backline(line))));
+                        token_handler
+                            .pending_elems
+                            .push((Cow::Borrowed(text), Some(Class::Backline(line))));
+                    }
+                    if current_expansion.is_none() {
+                        current_expansion =
+                            get_expansion(&mut token_handler, expanded_codes, line, span);
+                        expansion_start_tags = token_handler.closing_tags.clone();
+                    }
+                    if let Some(ref current_expansion) = current_expansion
+                        && current_expansion.span.lo() == span.hi()
+                    {
+                        start_expansion(&mut token_handler.pending_elems, current_expansion);
                     }
                 } else {
-                    token_handler.pending_elems.push((text, class));
+                    token_handler.pending_elems.push((Cow::Borrowed(text), class));
+
+                    let mut need_end = false;
+                    if let Some(ref current_expansion) = current_expansion {
+                        if current_expansion.span.lo() == span.hi() {
+                            start_expansion(&mut token_handler.pending_elems, current_expansion);
+                        } else if current_expansion.end_line == line
+                            && span.hi() >= current_expansion.span.hi()
+                        {
+                            need_end = true;
+                        }
+                    }
+                    if need_end {
+                        current_expansion = end_expansion(
+                            &mut token_handler,
+                            expanded_codes,
+                            &expansion_start_tags,
+                            line,
+                            span,
+                        );
+                    }
                 }
             }
             Highlight::EnterSpan { class } => {
@@ -440,6 +604,8 @@ enum Class {
     QuestionMark,
     Decoration(&'static str),
     Backline(u32),
+    /// Macro expansion.
+    Expansion,
 }
 
 impl Class {
@@ -489,6 +655,7 @@ impl Class {
             Class::QuestionMark => "question-mark",
             Class::Decoration(kind) => kind,
             Class::Backline(_) => "",
+            Class::Expansion => "",
         }
     }
 
@@ -513,7 +680,8 @@ impl Class {
             | Self::Lifetime
             | Self::QuestionMark
             | Self::Decoration(_)
-            | Self::Backline(_) => None,
+            | Self::Backline(_)
+            | Self::Expansion => None,
         }
     }
 }
@@ -628,6 +796,13 @@ impl Decorations {
     }
 }
 
+/// Convenient wrapper to create a [`Span`] from a position in the file.
+fn new_span(lo: u32, text: &str, file_span: Span) -> Span {
+    let hi = lo + text.len() as u32;
+    let file_lo = file_span.lo();
+    file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi))
+}
+
 /// Processes program tokens, classifying strings of text by highlighting
 /// category (`Class`).
 struct Classifier<'src> {
@@ -660,13 +835,6 @@ impl<'src> Classifier<'src> {
         }
     }
 
-    /// Convenient wrapper to create a [`Span`] from a position in the file.
-    fn new_span(&self, lo: u32, text: &str) -> Span {
-        let hi = lo + text.len() as u32;
-        let file_lo = self.file_span.lo();
-        self.file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi))
-    }
-
     /// Concatenate colons and idents as one when possible.
     fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
         let start = self.byte_pos as usize;
@@ -735,18 +903,18 @@ impl<'src> Classifier<'src> {
     /// The general structure for this method is to iterate over each token,
     /// possibly giving it an HTML span with a class specifying what flavor of
     /// token is used.
-    fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'src>)) {
+    fn highlight(mut self, sink: &mut dyn FnMut(Span, Highlight<'src>)) {
         loop {
             if let Some(decs) = self.decorations.as_mut() {
                 let byte_pos = self.byte_pos;
                 let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count();
                 for (_, kind) in decs.starts.drain(0..n_starts) {
-                    sink(Highlight::EnterSpan { class: Class::Decoration(kind) });
+                    sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Decoration(kind) });
                 }
 
                 let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count();
                 for _ in decs.ends.drain(0..n_ends) {
-                    sink(Highlight::ExitSpan);
+                    sink(DUMMY_SP, Highlight::ExitSpan);
                 }
             }
 
@@ -784,14 +952,22 @@ impl<'src> Classifier<'src> {
         &mut self,
         token: TokenKind,
         text: &'src str,
-        sink: &mut dyn FnMut(Highlight<'src>),
+        sink: &mut dyn FnMut(Span, Highlight<'src>),
         before: u32,
     ) {
         let lookahead = self.peek();
-        let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
-        let whitespace = |sink: &mut dyn FnMut(_)| {
+        let file_span = self.file_span;
+        let no_highlight = |sink: &mut dyn FnMut(_, _)| {
+            sink(new_span(before, text, file_span), Highlight::Token { text, class: None })
+        };
+        let whitespace = |sink: &mut dyn FnMut(_, _)| {
+            let mut start = 0u32;
             for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) {
-                sink(Highlight::Token { text: part, class: None });
+                sink(
+                    new_span(before + start, part, file_span),
+                    Highlight::Token { text: part, class: None },
+                );
+                start += part.len() as u32;
             }
         };
         let class = match token {
@@ -807,8 +983,8 @@ impl<'src> Classifier<'src> {
             // leading identifier.
             TokenKind::Bang if self.in_macro => {
                 self.in_macro = false;
-                sink(Highlight::Token { text, class: None });
-                sink(Highlight::ExitSpan);
+                sink(new_span(before, text, file_span), Highlight::Token { text, class: None });
+                sink(DUMMY_SP, Highlight::ExitSpan);
                 return;
             }
 
@@ -819,12 +995,18 @@ impl<'src> Classifier<'src> {
                 Some((TokenKind::Whitespace, _)) => return whitespace(sink),
                 Some((TokenKind::Ident, "mut")) => {
                     self.next();
-                    sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) });
+                    sink(
+                        DUMMY_SP,
+                        Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) },
+                    );
                     return;
                 }
                 Some((TokenKind::Ident, "const")) => {
                     self.next();
-                    sink(Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) });
+                    sink(
+                        DUMMY_SP,
+                        Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) },
+                    );
                     return;
                 }
                 _ => Class::RefKeyWord,
@@ -832,18 +1014,21 @@ impl<'src> Classifier<'src> {
             TokenKind::And => match self.tokens.peek() {
                 Some((TokenKind::And, _)) => {
                     self.next();
-                    sink(Highlight::Token { text: "&&", class: None });
+                    sink(DUMMY_SP, Highlight::Token { text: "&&", class: None });
                     return;
                 }
                 Some((TokenKind::Eq, _)) => {
                     self.next();
-                    sink(Highlight::Token { text: "&=", class: None });
+                    sink(DUMMY_SP, Highlight::Token { text: "&=", class: None });
                     return;
                 }
                 Some((TokenKind::Whitespace, _)) => return whitespace(sink),
                 Some((TokenKind::Ident, "mut")) => {
                     self.next();
-                    sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) });
+                    sink(
+                        DUMMY_SP,
+                        Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) },
+                    );
                     return;
                 }
                 _ => Class::RefKeyWord,
@@ -853,19 +1038,19 @@ impl<'src> Classifier<'src> {
             TokenKind::Eq => match lookahead {
                 Some(TokenKind::Eq) => {
                     self.next();
-                    sink(Highlight::Token { text: "==", class: None });
+                    sink(DUMMY_SP, Highlight::Token { text: "==", class: None });
                     return;
                 }
                 Some(TokenKind::Gt) => {
                     self.next();
-                    sink(Highlight::Token { text: "=>", class: None });
+                    sink(DUMMY_SP, Highlight::Token { text: "=>", class: None });
                     return;
                 }
                 _ => return no_highlight(sink),
             },
             TokenKind::Minus if lookahead == Some(TokenKind::Gt) => {
                 self.next();
-                sink(Highlight::Token { text: "->", class: None });
+                sink(DUMMY_SP, Highlight::Token { text: "->", class: None });
                 return;
             }
 
@@ -916,16 +1101,22 @@ impl<'src> Classifier<'src> {
                         self.next();
                         if let Some(TokenKind::OpenBracket) = self.peek() {
                             self.in_attribute = true;
-                            sink(Highlight::EnterSpan { class: Class::Attribute });
+                            sink(
+                                new_span(before, text, file_span),
+                                Highlight::EnterSpan { class: Class::Attribute },
+                            );
                         }
-                        sink(Highlight::Token { text: "#", class: None });
-                        sink(Highlight::Token { text: "!", class: None });
+                        sink(DUMMY_SP, Highlight::Token { text: "#", class: None });
+                        sink(DUMMY_SP, Highlight::Token { text: "!", class: None });
                         return;
                     }
                     // Case 2: #[outer_attribute]
                     Some(TokenKind::OpenBracket) => {
                         self.in_attribute = true;
-                        sink(Highlight::EnterSpan { class: Class::Attribute });
+                        sink(
+                            new_span(before, text, file_span),
+                            Highlight::EnterSpan { class: Class::Attribute },
+                        );
                     }
                     _ => (),
                 }
@@ -934,8 +1125,11 @@ impl<'src> Classifier<'src> {
             TokenKind::CloseBracket => {
                 if self.in_attribute {
                     self.in_attribute = false;
-                    sink(Highlight::Token { text: "]", class: None });
-                    sink(Highlight::ExitSpan);
+                    sink(
+                        new_span(before, text, file_span),
+                        Highlight::Token { text: "]", class: None },
+                    );
+                    sink(DUMMY_SP, Highlight::ExitSpan);
                     return;
                 }
                 return no_highlight(sink);
@@ -956,15 +1150,16 @@ impl<'src> Classifier<'src> {
             TokenKind::GuardedStrPrefix => return no_highlight(sink),
             TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
                 self.in_macro = true;
-                sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) });
-                sink(Highlight::Token { text, class: None });
+                let span = new_span(before, text, file_span);
+                sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Macro(span) });
+                sink(span, Highlight::Token { text, class: None });
                 return;
             }
             TokenKind::Ident => match get_real_ident_class(text, false) {
                 None => match text {
-                    "Option" | "Result" => Class::PreludeTy(self.new_span(before, text)),
+                    "Option" | "Result" => Class::PreludeTy(new_span(before, text, file_span)),
                     "Some" | "None" | "Ok" | "Err" => {
-                        Class::PreludeVal(self.new_span(before, text))
+                        Class::PreludeVal(new_span(before, text, file_span))
                     }
                     // "union" is a weak keyword and is only considered as a keyword when declaring
                     // a union type.
@@ -973,13 +1168,13 @@ impl<'src> Classifier<'src> {
                         self.in_macro_nonterminal = false;
                         Class::MacroNonTerminal
                     }
-                    "self" | "Self" => Class::Self_(self.new_span(before, text)),
-                    _ => Class::Ident(self.new_span(before, text)),
+                    "self" | "Self" => Class::Self_(new_span(before, text, file_span)),
+                    _ => Class::Ident(new_span(before, text, file_span)),
                 },
                 Some(c) => c,
             },
             TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => {
-                Class::Ident(self.new_span(before, text))
+                Class::Ident(new_span(before, text, file_span))
             }
             TokenKind::Lifetime { .. }
             | TokenKind::RawLifetime
@@ -988,8 +1183,13 @@ impl<'src> Classifier<'src> {
         };
         // Anything that didn't return above is the simple case where we the
         // class just spans a single token, so we can use the `string` method.
+        let mut start = 0u32;
         for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) {
-            sink(Highlight::Token { text: part, class: Some(class) });
+            sink(
+                new_span(before + start, part, file_span),
+                Highlight::Token { text: part, class: Some(class) },
+            );
+            start += part.len() as u32;
         }
     }
 
@@ -1042,9 +1242,9 @@ fn exit_span(out: &mut impl Write, closing_tag: &str) {
 /// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
 /// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then
 /// generate a link for this element (which corresponds to where its definition is located).
-fn string<T: Display, W: Write>(
+fn string<W: Write>(
     out: &mut W,
-    text: T,
+    text: EscapeBodyText<'_>,
     klass: Option<Class>,
     href_context: &Option<HrefContext<'_, '_>>,
     open_tag: bool,
@@ -1052,6 +1252,9 @@ fn string<T: Display, W: Write>(
 ) {
     if let Some(Class::Backline(line)) = klass {
         write_line_number_callback(out, line, "\n");
+    } else if let Some(Class::Expansion) = klass {
+        // This has already been escaped so we get the text to write it directly.
+        out.write_str(text.0).unwrap();
     } else if let Some(closing_tag) =
         string_without_closing_tag(out, text, klass, href_context, open_tag)
     {
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 2782e8e0058..5db742bdebf 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -27,6 +27,7 @@ pub(crate) struct Layout {
 
 pub(crate) struct Page<'a> {
     pub(crate) title: &'a str,
+    pub(crate) short_title: &'a str,
     pub(crate) css_class: &'a str,
     pub(crate) root_path: &'a str,
     pub(crate) static_root_path: Option<&'a str>,
diff --git a/src/librustdoc/html/macro_expansion.rs b/src/librustdoc/html/macro_expansion.rs
new file mode 100644
index 00000000000..9098e92a5cd
--- /dev/null
+++ b/src/librustdoc/html/macro_expansion.rs
@@ -0,0 +1,156 @@
+use rustc_ast::visit::{Visitor, walk_crate, walk_expr, walk_item, walk_pat, walk_stmt};
+use rustc_ast::{Crate, Expr, Item, Pat, Stmt};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_span::source_map::SourceMap;
+use rustc_span::{BytePos, Span};
+
+use crate::config::{OutputFormat, RenderOptions};
+
+/// It returns the expanded macros correspondence map.
+pub(crate) fn source_macro_expansion(
+    krate: &Crate,
+    render_options: &RenderOptions,
+    output_format: OutputFormat,
+    source_map: &SourceMap,
+) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
+    if output_format == OutputFormat::Html
+        && !render_options.html_no_source
+        && render_options.generate_macro_expansion
+    {
+        let mut expanded_visitor = ExpandedCodeVisitor { expanded_codes: Vec::new(), source_map };
+        walk_crate(&mut expanded_visitor, krate);
+        expanded_visitor.compute_expanded()
+    } else {
+        Default::default()
+    }
+}
+
+/// Contains information about macro expansion in the source code pages.
+#[derive(Debug)]
+pub(crate) struct ExpandedCode {
+    /// The line where the macro expansion starts.
+    pub(crate) start_line: u32,
+    /// The line where the macro expansion ends.
+    pub(crate) end_line: u32,
+    /// The source code of the expanded macro.
+    pub(crate) code: String,
+    /// The span of macro callsite.
+    pub(crate) span: Span,
+}
+
+/// Contains temporary information of macro expanded code.
+///
+/// As we go through the HIR visitor, if any span overlaps with another, they will
+/// both be merged.
+struct ExpandedCodeInfo {
+    /// Callsite of the macro.
+    span: Span,
+    /// Expanded macro source code (HTML escaped).
+    code: String,
+    /// Span of macro-generated code.
+    expanded_span: Span,
+}
+
+/// HIR visitor which retrieves expanded macro.
+///
+/// Once done, the `expanded_codes` will be transformed into a vec of [`ExpandedCode`]
+/// which contains the information needed when running the source code highlighter.
+pub(crate) struct ExpandedCodeVisitor<'ast> {
+    expanded_codes: Vec<ExpandedCodeInfo>,
+    source_map: &'ast SourceMap,
+}
+
+impl<'ast> ExpandedCodeVisitor<'ast> {
+    fn handle_new_span<F: Fn() -> String>(&mut self, new_span: Span, f: F) {
+        if new_span.is_dummy() || !new_span.from_expansion() {
+            return;
+        }
+        let callsite_span = new_span.source_callsite();
+        if let Some(index) =
+            self.expanded_codes.iter().position(|info| info.span.overlaps(callsite_span))
+        {
+            let info = &mut self.expanded_codes[index];
+            if new_span.contains(info.expanded_span) {
+                // New macro expansion recursively contains the old one, so replace it.
+                info.span = callsite_span;
+                info.expanded_span = new_span;
+                info.code = f();
+            } else {
+                // We push the new item after the existing one.
+                let expanded_code = &mut self.expanded_codes[index];
+                expanded_code.code.push('\n');
+                expanded_code.code.push_str(&f());
+                let lo = BytePos(expanded_code.expanded_span.lo().0.min(new_span.lo().0));
+                let hi = BytePos(expanded_code.expanded_span.hi().0.min(new_span.hi().0));
+                expanded_code.expanded_span = expanded_code.expanded_span.with_lo(lo).with_hi(hi);
+            }
+        } else {
+            // We add a new item.
+            self.expanded_codes.push(ExpandedCodeInfo {
+                span: callsite_span,
+                code: f(),
+                expanded_span: new_span,
+            });
+        }
+    }
+
+    fn compute_expanded(mut self) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
+        self.expanded_codes.sort_unstable_by(|item1, item2| item1.span.cmp(&item2.span));
+        let mut expanded: FxHashMap<BytePos, Vec<ExpandedCode>> = FxHashMap::default();
+        for ExpandedCodeInfo { span, code, .. } in self.expanded_codes {
+            if let Ok(lines) = self.source_map.span_to_lines(span)
+                && !lines.lines.is_empty()
+            {
+                let mut out = String::new();
+                super::highlight::write_code(&mut out, &code, None, None, None);
+                let first = lines.lines.first().unwrap();
+                let end = lines.lines.last().unwrap();
+                expanded.entry(lines.file.start_pos).or_default().push(ExpandedCode {
+                    start_line: first.line_index as u32 + 1,
+                    end_line: end.line_index as u32 + 1,
+                    code: out,
+                    span,
+                });
+            }
+        }
+        expanded
+    }
+}
+
+// We need to use the AST pretty printing because:
+//
+// 1. HIR pretty printing doesn't display accurately the code (like `impl Trait`).
+// 2. `SourceMap::snippet_opt` might fail if the source is not available.
+impl<'ast> Visitor<'ast> for ExpandedCodeVisitor<'ast> {
+    fn visit_expr(&mut self, expr: &'ast Expr) {
+        if expr.span.from_expansion() {
+            self.handle_new_span(expr.span, || rustc_ast_pretty::pprust::expr_to_string(expr));
+        } else {
+            walk_expr(self, expr);
+        }
+    }
+
+    fn visit_item(&mut self, item: &'ast Item) {
+        if item.span.from_expansion() {
+            self.handle_new_span(item.span, || rustc_ast_pretty::pprust::item_to_string(item));
+        } else {
+            walk_item(self, item);
+        }
+    }
+
+    fn visit_stmt(&mut self, stmt: &'ast Stmt) {
+        if stmt.span.from_expansion() {
+            self.handle_new_span(stmt.span, || rustc_ast_pretty::pprust::stmt_to_string(stmt));
+        } else {
+            walk_stmt(self, stmt);
+        }
+    }
+
+    fn visit_pat(&mut self, pat: &'ast Pat) {
+        if pat.span.from_expansion() {
+            self.handle_new_span(pat.span, || rustc_ast_pretty::pprust::pat_to_string(pat));
+        } else {
+            walk_pat(self, pat);
+        }
+    }
+}
diff --git a/src/librustdoc/html/mod.rs b/src/librustdoc/html/mod.rs
index 481ed16c05f..d42f4782845 100644
--- a/src/librustdoc/html/mod.rs
+++ b/src/librustdoc/html/mod.rs
@@ -3,6 +3,7 @@ pub(crate) mod format;
 pub(crate) mod highlight;
 pub(crate) mod layout;
 mod length_limit;
+pub(crate) mod macro_expansion;
 // used by the error-index generator, so it needs to be public
 pub mod markdown;
 pub(crate) mod render;
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 5ceb1fc988d..faaecaf0cbb 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -12,7 +12,7 @@ use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
 use rustc_span::edition::Edition;
-use rustc_span::{FileName, Symbol, sym};
+use rustc_span::{BytePos, FileName, Symbol, sym};
 use tracing::info;
 
 use super::print_item::{full_path, print_item, print_item_path};
@@ -28,6 +28,7 @@ use crate::formats::FormatRenderer;
 use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
 use crate::html::escape::Escape;
+use crate::html::macro_expansion::ExpandedCode;
 use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary};
 use crate::html::render::write_shared::write_shared;
 use crate::html::url_parts_builder::UrlPartsBuilder;
@@ -139,6 +140,7 @@ pub(crate) struct SharedContext<'tcx> {
     /// Correspondence map used to link types used in the source code pages to allow to click on
     /// links to jump to the type's definition.
     pub(crate) span_correspondence_map: FxHashMap<rustc_span::Span, LinkFromSrc>,
+    pub(crate) expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>,
     /// The [`Cache`] used during rendering.
     pub(crate) cache: Cache,
     pub(crate) call_locations: AllCallLocations,
@@ -204,6 +206,18 @@ impl<'tcx> Context<'tcx> {
         if !is_module {
             title.push_str(it.name.unwrap().as_str());
         }
+        let short_title;
+        let short_title = if is_module {
+            let module_name = self.current.last().unwrap();
+            short_title = if it.is_crate() {
+                format!("Crate {module_name}")
+            } else {
+                format!("Module {module_name}")
+            };
+            &short_title[..]
+        } else {
+            it.name.as_ref().unwrap().as_str()
+        };
         if !it.is_primitive() && !it.is_keyword() {
             if !is_module {
                 title.push_str(" in ");
@@ -240,6 +254,7 @@ impl<'tcx> Context<'tcx> {
                 root_path: &self.root_path(),
                 static_root_path: self.shared.static_root_path.as_deref(),
                 title: &title,
+                short_title,
                 description: &desc,
                 resource_suffix: &self.shared.resource_suffix,
                 rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo),
@@ -445,20 +460,13 @@ impl<'tcx> Context<'tcx> {
     }
 }
 
-/// Generates the documentation for `crate` into the directory `dst`
-impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
-    fn descr() -> &'static str {
-        "html"
-    }
-
-    const RUN_ON_MODULE: bool = true;
-    type ModuleData = ContextInfo;
-
-    fn init(
+impl<'tcx> Context<'tcx> {
+    pub(crate) fn init(
         krate: clean::Crate,
         options: RenderOptions,
         cache: Cache,
         tcx: TyCtxt<'tcx>,
+        expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>,
     ) -> Result<(Self, clean::Crate), Error> {
         // need to save a copy of the options for rendering the index page
         let md_opts = options.clone();
@@ -566,6 +574,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             cache,
             call_locations,
             should_merge: options.should_merge,
+            expanded_codes,
         };
 
         let dst = output;
@@ -591,6 +600,16 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
 
         Ok((cx, krate))
     }
+}
+
+/// Generates the documentation for `crate` into the directory `dst`
+impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
+    fn descr() -> &'static str {
+        "html"
+    }
+
+    const RUN_ON_MODULE: bool = true;
+    type ModuleData = ContextInfo;
 
     fn save_module_data(&mut self) -> Self::ModuleData {
         self.deref_id_map.borrow_mut().clear();
@@ -617,6 +636,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         let shared = &self.shared;
         let mut page = layout::Page {
             title: "List of all items in this crate",
+            short_title: "All",
             css_class: "mod sys",
             root_path: "../",
             static_root_path: shared.static_root_path.as_deref(),
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index a46253237db..673947ad308 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -130,11 +130,11 @@ pub(crate) struct IndexItem {
     pub(crate) ty: ItemType,
     pub(crate) defid: Option<DefId>,
     pub(crate) name: Symbol,
-    pub(crate) path: String,
+    pub(crate) module_path: Vec<Symbol>,
     pub(crate) desc: String,
     pub(crate) parent: Option<DefId>,
-    pub(crate) parent_idx: Option<isize>,
-    pub(crate) exact_path: Option<String>,
+    pub(crate) parent_idx: Option<usize>,
+    pub(crate) exact_module_path: Option<Vec<Symbol>>,
     pub(crate) impl_id: Option<DefId>,
     pub(crate) search_type: Option<IndexItemFunctionType>,
     pub(crate) aliases: Box<[Symbol]>,
@@ -150,6 +150,19 @@ struct RenderType {
 }
 
 impl RenderType {
+    fn size(&self) -> usize {
+        let mut size = 1;
+        if let Some(generics) = &self.generics {
+            size += generics.iter().map(RenderType::size).sum::<usize>();
+        }
+        if let Some(bindings) = &self.bindings {
+            for (_, constraints) in bindings.iter() {
+                size += 1;
+                size += constraints.iter().map(RenderType::size).sum::<usize>();
+            }
+        }
+        size
+    }
     // Types are rendered as lists of lists, because that's pretty compact.
     // The contents of the lists are always integers in self-terminating hex
     // form, handled by `RenderTypeId::write_to_string`, so no commas are
@@ -191,6 +204,62 @@ impl RenderType {
             write_optional_id(self.id, string);
         }
     }
+    fn read_from_bytes(string: &[u8]) -> (RenderType, usize) {
+        let mut i = 0;
+        if string[i] == b'{' {
+            i += 1;
+            let (id, offset) = RenderTypeId::read_from_bytes(&string[i..]);
+            i += offset;
+            let generics = if string[i] == b'{' {
+                i += 1;
+                let mut generics = Vec::new();
+                while string[i] != b'}' {
+                    let (ty, offset) = RenderType::read_from_bytes(&string[i..]);
+                    i += offset;
+                    generics.push(ty);
+                }
+                assert!(string[i] == b'}');
+                i += 1;
+                Some(generics)
+            } else {
+                None
+            };
+            let bindings = if string[i] == b'{' {
+                i += 1;
+                let mut bindings = Vec::new();
+                while string[i] == b'{' {
+                    i += 1;
+                    let (binding, boffset) = RenderTypeId::read_from_bytes(&string[i..]);
+                    i += boffset;
+                    let mut bconstraints = Vec::new();
+                    assert!(string[i] == b'{');
+                    i += 1;
+                    while string[i] != b'}' {
+                        let (constraint, coffset) = RenderType::read_from_bytes(&string[i..]);
+                        i += coffset;
+                        bconstraints.push(constraint);
+                    }
+                    assert!(string[i] == b'}');
+                    i += 1;
+                    bindings.push((binding.unwrap(), bconstraints));
+                    assert!(string[i] == b'}');
+                    i += 1;
+                }
+                assert!(string[i] == b'}');
+                i += 1;
+                Some(bindings)
+            } else {
+                None
+            };
+            assert!(string[i] == b'}');
+            i += 1;
+            (RenderType { id, generics, bindings }, i)
+        } else {
+            let (id, offset) = RenderTypeId::read_from_bytes(string);
+            i += offset;
+            (RenderType { id, generics: None, bindings: None }, i)
+        }
+    }
 }
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -212,7 +281,20 @@ impl RenderTypeId {
             RenderTypeId::Index(idx) => (*idx).try_into().unwrap(),
             _ => panic!("must convert render types to indexes before serializing"),
         };
-        search_index::encode::write_vlqhex_to_string(id, string);
+        search_index::encode::write_signed_vlqhex_to_string(id, string);
+    }
+    fn read_from_bytes(string: &[u8]) -> (Option<RenderTypeId>, usize) {
+        let Some((value, offset)) = search_index::encode::read_signed_vlqhex_from_string(string)
+        else {
+            return (None, 0);
+        };
+        let value = isize::try_from(value).unwrap();
+        let ty = match value {
+            ..0 => Some(RenderTypeId::Index(value)),
+            0 => None,
+            1.. => Some(RenderTypeId::Index(value - 1)),
+        };
+        (ty, offset)
     }
 }
 
@@ -226,12 +308,64 @@ pub(crate) struct IndexItemFunctionType {
 }
 
 impl IndexItemFunctionType {
-    fn write_to_string<'a>(
-        &'a self,
-        string: &mut String,
-        backref_queue: &mut VecDeque<&'a IndexItemFunctionType>,
-    ) {
-        assert!(backref_queue.len() <= 16);
+    fn size(&self) -> usize {
+        self.inputs.iter().map(RenderType::size).sum::<usize>()
+            + self.output.iter().map(RenderType::size).sum::<usize>()
+            + self
+                .where_clause
+                .iter()
+                .map(|constraints| constraints.iter().map(RenderType::size).sum::<usize>())
+                .sum::<usize>()
+    }
+    fn read_from_string_without_param_names(string: &[u8]) -> (IndexItemFunctionType, usize) {
+        let mut i = 0;
+        if string[i] == b'`' {
+            return (
+                IndexItemFunctionType {
+                    inputs: Vec::new(),
+                    output: Vec::new(),
+                    where_clause: Vec::new(),
+                    param_names: Vec::new(),
+                },
+                1,
+            );
+        }
+        assert_eq!(b'{', string[i]);
+        i += 1;
+        fn read_args_from_string(string: &[u8]) -> (Vec<RenderType>, usize) {
+            let mut i = 0;
+            let mut params = Vec::new();
+            if string[i] == b'{' {
+                // multiple params
+                i += 1;
+                while string[i] != b'}' {
+                    let (ty, offset) = RenderType::read_from_bytes(&string[i..]);
+                    i += offset;
+                    params.push(ty);
+                }
+                i += 1;
+            } else if string[i] != b'}' {
+                let (tyid, offset) = RenderTypeId::read_from_bytes(&string[i..]);
+                params.push(RenderType { id: tyid, generics: None, bindings: None });
+                i += offset;
+            }
+            (params, i)
+        }
+        let (inputs, offset) = read_args_from_string(&string[i..]);
+        i += offset;
+        let (output, offset) = read_args_from_string(&string[i..]);
+        i += offset;
+        let mut where_clause = Vec::new();
+        while string[i] != b'}' {
+            let (constraint, offset) = read_args_from_string(&string[i..]);
+            i += offset;
+            where_clause.push(constraint);
+        }
+        assert_eq!(b'}', string[i], "{} {}", String::from_utf8_lossy(&string), i);
+        i += 1;
+        (IndexItemFunctionType { inputs, output, where_clause, param_names: Vec::new() }, i)
+    }
+    fn write_to_string_without_param_names<'a>(&'a self, string: &mut String) {
         // If we couldn't figure out a type, just write 0,
         // which is encoded as `` ` `` (see RenderTypeId::write_to_string).
         let has_missing = self
@@ -241,18 +375,7 @@ impl IndexItemFunctionType {
             .any(|i| i.id.is_none() && i.generics.is_none());
         if has_missing {
             string.push('`');
-        } else if let Some(idx) = backref_queue.iter().position(|other| *other == self) {
-            // The backref queue has 16 items, so backrefs use
-            // a single hexit, disjoint from the ones used for numbers.
-            string.push(
-                char::try_from('0' as u32 + u32::try_from(idx).unwrap())
-                    .expect("last possible value is '?'"),
-            );
         } else {
-            backref_queue.push_front(self);
-            if backref_queue.len() > 16 {
-                backref_queue.pop_back();
-            }
             string.push('{');
             match &self.inputs[..] {
                 [one] if one.generics.is_none() && one.bindings.is_none() => {
@@ -906,6 +1029,7 @@ fn assoc_const(
 ) -> impl fmt::Display {
     let tcx = cx.tcx();
     fmt::from_fn(move |w| {
+        render_attributes_in_code(w, it, &" ".repeat(indent), cx);
         write!(
             w,
             "{indent}{vis}const <a{href} class=\"constant\">{name}</a>{generics}: {ty}",
@@ -1013,10 +1137,10 @@ fn assoc_method(
         let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
             header_len += 4;
             let indent_str = "    ";
-            write!(w, "{}", render_attributes_in_pre(meth, indent_str, cx))?;
+            render_attributes_in_code(w, meth, indent_str, cx);
             (4, indent_str, Ending::NoNewline)
         } else {
-            render_attributes_in_code(w, meth, cx);
+            render_attributes_in_code(w, meth, "", cx);
             (0, "", Ending::Newline)
         };
         write!(
@@ -1186,28 +1310,28 @@ fn render_assoc_item(
     })
 }
 
-// When an attribute is rendered inside a `<pre>` tag, it is formatted using
-// a whitespace prefix and newline.
-fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display {
-    fmt::from_fn(move |f| {
-        for a in it.attributes(cx.tcx(), cx.cache()) {
-            writeln!(f, "{prefix}{a}")?;
-        }
-        Ok(())
-    })
-}
-
 struct CodeAttribute(String);
 
-fn render_code_attribute(code_attr: CodeAttribute, w: &mut impl fmt::Write) {
-    write!(w, "<div class=\"code-attribute\">{}</div>", code_attr.0).unwrap();
+fn render_code_attribute(prefix: &str, code_attr: CodeAttribute, w: &mut impl fmt::Write) {
+    write!(
+        w,
+        "<div class=\"code-attribute\">{prefix}{attr}</div>",
+        prefix = prefix,
+        attr = code_attr.0
+    )
+    .unwrap();
 }
 
 // When an attribute is rendered inside a <code> tag, it is formatted using
 // a div to produce a newline after it.
-fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
+fn render_attributes_in_code(
+    w: &mut impl fmt::Write,
+    it: &clean::Item,
+    prefix: &str,
+    cx: &Context<'_>,
+) {
     for attr in it.attributes(cx.tcx(), cx.cache()) {
-        render_code_attribute(CodeAttribute(attr), w);
+        render_code_attribute(prefix, CodeAttribute(attr), w);
     }
 }
 
@@ -1219,7 +1343,7 @@ fn render_repr_attributes_in_code(
     item_type: ItemType,
 ) {
     if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) {
-        render_code_attribute(CodeAttribute(repr), w);
+        render_code_attribute("", CodeAttribute(repr), w);
     }
 }
 
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 759f53974f5..b86a2c94697 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -20,8 +20,8 @@ use super::{
     AssocItemLink, AssocItemRender, Context, ImplRenderingParameters, RenderMode,
     collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
     item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
-    render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
-    render_impl, render_repr_attributes_in_code, render_rightside, render_stability_since_raw,
+    render_assoc_item, render_assoc_items, render_attributes_in_code, render_impl,
+    render_repr_attributes_in_code, render_rightside, render_stability_since_raw,
     render_stability_since_raw_with_extra, write_section_heading,
 };
 use crate::clean;
@@ -35,6 +35,7 @@ use crate::html::format::{
     visibility_print_with_space,
 };
 use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
+use crate::html::render::sidebar::filters;
 use crate::html::render::{document_full, document_item_info};
 use crate::html::url_parts_builder::UrlPartsBuilder;
 
@@ -106,13 +107,6 @@ macro_rules! item_template_methods {
         }
         item_template_methods!($($rest)*);
     };
-    (render_attributes_in_pre $($rest:tt)*) => {
-        fn render_attributes_in_pre(&self) -> impl fmt::Display {
-            let (item, cx) = self.item_and_cx();
-            render_attributes_in_pre(item, "", cx)
-        }
-        item_template_methods!($($rest)*);
-    };
     (render_assoc_items $($rest:tt)*) => {
         fn render_assoc_items(&self) -> impl fmt::Display {
             let (item, cx) = self.item_and_cx();
@@ -456,7 +450,12 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
                     write!(
                         w,
                         "<dt{id}>\
-                            <code>{vis}{imp}</code>{stab_tags}\
+                            <code>"
+                    )?;
+                    render_attributes_in_code(w, myitem, "", cx);
+                    write!(
+                        w,
+                        "{vis}{imp}</code>{stab_tags}\
                         </dt>",
                         vis = visibility_print_with_space(myitem, cx),
                         imp = import.print(cx)
@@ -624,11 +623,11 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp
         let notable_traits = notable_traits_button(&f.decl.output, cx).maybe_display();
 
         wrap_item(w, |w| {
+            render_attributes_in_code(w, it, "", cx);
             write!(
                 w,
-                "{attrs}{vis}{constness}{asyncness}{safety}{abi}fn \
+                "{vis}{constness}{asyncness}{safety}{abi}fn \
                 {name}{generics}{decl}{notable_traits}{where_clause}",
-                attrs = render_attributes_in_pre(it, "", cx),
                 vis = visibility,
                 constness = constness,
                 asyncness = asyncness,
@@ -665,10 +664,10 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt:
 
         // Output the trait definition
         wrap_item(w, |mut w| {
+            render_attributes_in_code(&mut w, it, "", cx);
             write!(
                 w,
-                "{attrs}{vis}{safety}{is_auto}trait {name}{generics}{bounds}",
-                attrs = render_attributes_in_pre(it, "", cx),
+                "{vis}{safety}{is_auto}trait {name}{generics}{bounds}",
                 vis = visibility_print_with_space(it, cx),
                 safety = t.safety(tcx).print_with_space(),
                 is_auto = if t.is_auto(tcx) { "auto " } else { "" },
@@ -1239,10 +1238,10 @@ fn item_trait_alias(
 ) -> impl fmt::Display {
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
+            render_attributes_in_code(w, it, "", cx);
             write!(
                 w,
-                "{attrs}trait {name}{generics} = {bounds}{where_clause};",
-                attrs = render_attributes_in_pre(it, "", cx),
+                "trait {name}{generics} = {bounds}{where_clause};",
                 name = it.name.unwrap(),
                 generics = t.generics.print(cx),
                 bounds = print_bounds(&t.bounds, true, cx),
@@ -1267,10 +1266,10 @@ fn item_trait_alias(
 fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) -> impl fmt::Display {
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
+            render_attributes_in_code(w, it, "", cx);
             write!(
                 w,
-                "{attrs}{vis}type {name}{generics}{where_clause} = {type_};",
-                attrs = render_attributes_in_pre(it, "", cx),
+                "{vis}type {name}{generics}{where_clause} = {type_};",
                 vis = visibility_print_with_space(it, cx),
                 name = it.name.unwrap(),
                 generics = t.generics.print(cx),
@@ -1451,7 +1450,21 @@ item_template!(
 
 impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
     fn render_union(&self) -> impl Display {
-        render_union(self.it, Some(self.generics), self.fields, self.cx)
+        render_union(
+            self.it,
+            Some(self.generics),
+            self.fields,
+            self.def_id,
+            self.is_type_alias,
+            self.cx,
+        )
+    }
+
+    fn print_field_attrs(&self, field: &'a clean::Item) -> impl Display {
+        fmt::from_fn(move |w| {
+            render_attributes_in_code(w, field, "", self.cx);
+            Ok(())
+        })
     }
 
     fn document_field(&self, field: &'a clean::Item) -> impl Display {
@@ -1478,27 +1491,6 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
             _ => None,
         })
     }
-
-    fn render_attributes_in_pre(&self) -> impl fmt::Display {
-        fmt::from_fn(move |f| {
-            if self.is_type_alias {
-                // For now the only attributes we render for type aliases are `repr` attributes.
-                if let Some(repr) = clean::repr_attributes(
-                    self.cx.tcx(),
-                    self.cx.cache(),
-                    self.def_id,
-                    ItemType::Union,
-                ) {
-                    writeln!(f, "{repr}")?;
-                };
-            } else {
-                for a in self.it.attributes(self.cx.tcx(), self.cx.cache()) {
-                    writeln!(f, "{a}")?;
-                }
-            }
-            Ok(())
-        })
-    }
 }
 
 fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt::Display {
@@ -1562,7 +1554,7 @@ impl<'clean> DisplayEnum<'clean> {
                 // For now the only attributes we render for type aliases are `repr` attributes.
                 render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum);
             } else {
-                render_attributes_in_code(w, it, cx);
+                render_attributes_in_code(w, it, "", cx);
             }
             write!(
                 w,
@@ -1701,7 +1693,7 @@ fn render_enum_fields(
                 if v.is_stripped() {
                     continue;
                 }
-                write!(w, "{}", render_attributes_in_pre(v, TAB, cx))?;
+                render_attributes_in_code(w, v, TAB, cx);
                 w.write_str(TAB)?;
                 match v.kind {
                     clean::VariantItem(ref var) => match var.kind {
@@ -1785,6 +1777,7 @@ fn item_variants(
                 )
                 .maybe_display()
             )?;
+            render_attributes_in_code(w, variant, "", cx);
             if let clean::VariantItem(ref var) = variant.kind
                 && let clean::VariantKind::CLike = var.kind
             {
@@ -1858,7 +1851,12 @@ fn item_variants(
                                 "<div class=\"sub-variant-field\">\
                                     <span id=\"{id}\" class=\"section-header\">\
                                         <a href=\"#{id}\" class=\"anchor field\">§</a>\
-                                        <code>{f}: {t}</code>\
+                                        <code>"
+                            )?;
+                            render_attributes_in_code(w, field, "", cx);
+                            write!(
+                                w,
+                                "{f}: {t}</code>\
                                     </span>\
                                     {doc}\
                                 </div>",
@@ -1881,6 +1879,7 @@ fn item_macro(cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) -> impl fmt:
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
             // FIXME: Also print `#[doc(hidden)]` for `macro_rules!` if it `is_doc_hidden`.
+            render_attributes_in_code(w, it, "", cx);
             if !t.macro_rules {
                 write!(w, "{}", visibility_print_with_space(it, cx))?;
             }
@@ -1949,7 +1948,7 @@ fn item_constant(
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
             let tcx = cx.tcx();
-            render_attributes_in_code(w, it, cx);
+            render_attributes_in_code(w, it, "", cx);
 
             write!(
                 w,
@@ -2017,7 +2016,7 @@ impl<'a> DisplayStruct<'a> {
                 // For now the only attributes we render for type aliases are `repr` attributes.
                 render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct);
             } else {
-                render_attributes_in_code(w, it, cx);
+                render_attributes_in_code(w, it, "", cx);
             }
             write!(
                 w,
@@ -2093,10 +2092,15 @@ fn item_fields(
                     w,
                     "<span id=\"{id}\" class=\"{item_type} section-header\">\
                         <a href=\"#{id}\" class=\"anchor field\">§</a>\
-                        <code>{field_name}: {ty}</code>\
+                        <code>",
+                    item_type = ItemType::StructField,
+                )?;
+                render_attributes_in_code(w, field, "", cx);
+                write!(
+                    w,
+                    "{field_name}: {ty}</code>\
                     </span>\
                     {doc}",
-                    item_type = ItemType::StructField,
                     ty = ty.print(cx),
                     doc = document(cx, field, Some(it), HeadingOffset::H3),
                 )?;
@@ -2114,7 +2118,7 @@ fn item_static(
 ) -> impl fmt::Display {
     fmt::from_fn(move |w| {
         wrap_item(w, |w| {
-            render_attributes_in_code(w, it, cx);
+            render_attributes_in_code(w, it, "", cx);
             write!(
                 w,
                 "{vis}{safe}static {mutability}{name}: {typ}",
@@ -2134,7 +2138,7 @@ fn item_foreign_type(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display {
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
             w.write_str("extern {\n")?;
-            render_attributes_in_code(w, it, cx);
+            render_attributes_in_code(w, it, "", cx);
             write!(w, "    {}type {};\n}}", visibility_print_with_space(it, cx), it.name.unwrap(),)
         })?;
 
@@ -2357,9 +2361,17 @@ fn render_union(
     it: &clean::Item,
     g: Option<&clean::Generics>,
     fields: &[clean::Item],
+    def_id: DefId,
+    is_type_alias: bool,
     cx: &Context<'_>,
 ) -> impl Display {
     fmt::from_fn(move |mut f| {
+        if is_type_alias {
+            // For now the only attributes we render for type aliases are `repr` attributes.
+            render_repr_attributes_in_code(f, cx, def_id, ItemType::Union);
+        } else {
+            render_attributes_in_code(f, it, "", cx);
+        }
         write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?;
 
         let where_displayed = if let Some(generics) = g {
@@ -2389,6 +2401,7 @@ fn render_union(
 
         for field in fields {
             if let clean::StructFieldItem(ref ty) = field.kind {
+                render_attributes_in_code(&mut f, field, "    ", cx);
                 writeln!(
                     f,
                     "    {}{}: {},",
@@ -2480,11 +2493,15 @@ fn render_struct_fields(
                 if toggle {
                     toggle_open(&mut *w, format_args!("{count_fields} fields"));
                 }
+                if has_visible_fields {
+                    writeln!(w)?;
+                }
                 for field in fields {
                     if let clean::StructFieldItem(ref ty) = field.kind {
-                        write!(
+                        render_attributes_in_code(w, field, &format!("{tab}    "), cx);
+                        writeln!(
                             w,
-                            "\n{tab}    {vis}{name}: {ty},",
+                            "{tab}    {vis}{name}: {ty},",
                             vis = visibility_print_with_space(field, cx),
                             name = field.name.unwrap(),
                             ty = ty.print(cx)
@@ -2494,12 +2511,12 @@ fn render_struct_fields(
 
                 if has_visible_fields {
                     if has_stripped_entries {
-                        write!(
+                        writeln!(
                             w,
-                            "\n{tab}    <span class=\"comment\">/* private fields */</span>"
+                            "{tab}    <span class=\"comment\">/* private fields */</span>"
                         )?;
                     }
-                    write!(w, "\n{tab}")?;
+                    write!(w, "{tab}")?;
                 } else if has_stripped_entries {
                     write!(w, " <span class=\"comment\">/* private fields */</span> ")?;
                 }
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index e2f86b8a854..dddc087d124 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -1,72 +1,1169 @@
 pub(crate) mod encode;
 
+use std::collections::BTreeSet;
 use std::collections::hash_map::Entry;
-use std::collections::{BTreeMap, VecDeque};
+use std::path::Path;
 
-use encode::{bitmap_to_string, write_vlqhex_to_string};
 use rustc_ast::join_path_syms;
-use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
 use rustc_middle::ty::TyCtxt;
 use rustc_span::def_id::DefId;
 use rustc_span::sym;
 use rustc_span::symbol::{Symbol, kw};
-use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
+use serde::de::{self, Deserializer, Error as _};
+use serde::ser::{SerializeSeq, Serializer};
+use serde::{Deserialize, Serialize};
+use stringdex::internals as stringdex_internals;
 use thin_vec::ThinVec;
 use tracing::instrument;
 
 use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
 use crate::clean::{self, utils};
+use crate::error::Error;
 use crate::formats::cache::{Cache, OrphanImplItem};
 use crate::formats::item_type::ItemType;
 use crate::html::markdown::short_markdown_summary;
-use crate::html::render::ordered_json::OrderedJson;
 use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId};
 
-/// The serialized search description sharded version
-///
-/// The `index` is a JSON-encoded list of names and other information.
-///
-/// The desc has newlined descriptions, split up by size into 128KiB shards.
-/// For example, `(4, "foo\nbar\nbaz\nquux")`.
-///
-/// There is no single, optimal size for these shards, because it depends on
-/// configuration values that we can't predict or control, such as the version
-/// of HTTP used (HTTP/1.1 would work better with larger files, while HTTP/2
-/// and 3 are more agnostic), transport compression (gzip, zstd, etc), whether
-/// the search query is going to produce a large number of results or a small
-/// number, the bandwidth delay product of the network...
-///
-/// Gzipping some standard library descriptions to guess what transport
-/// compression will do, the compressed file sizes can be as small as 4.9KiB
-/// or as large as 18KiB (ignoring the final 1.9KiB shard of leftovers).
-/// A "reasonable" range for files is for them to be bigger than 1KiB,
-/// since that's about the amount of data that can be transferred in a
-/// single TCP packet, and 64KiB, the maximum amount of data that
-/// TCP can transfer in a single round trip without extensions.
-///
-/// [1]: https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media
-/// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept
-/// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
 pub(crate) struct SerializedSearchIndex {
-    pub(crate) index: OrderedJson,
-    pub(crate) desc: Vec<(usize, String)>,
+    // data from disk
+    names: Vec<String>,
+    path_data: Vec<Option<PathData>>,
+    entry_data: Vec<Option<EntryData>>,
+    descs: Vec<String>,
+    function_data: Vec<Option<FunctionData>>,
+    alias_pointers: Vec<Option<usize>>,
+    // inverted index for concrete types and generics
+    type_data: Vec<Option<TypeData>>,
+    /// inverted index of generics
+    ///
+    /// - The outermost list has one entry per alpha-normalized generic.
+    ///
+    /// - The second layer is sorted by number of types that appear in the
+    ///   type signature. The search engine iterates over these in order from
+    ///   smallest to largest. Functions with less stuff in their type
+    ///   signature are more likely to be what the user wants, because we never
+    ///   show functions that are *missing* parts of the query, so removing..
+    ///
+    /// - The final layer is the list of functions.
+    generic_inverted_index: Vec<Vec<Vec<u32>>>,
+    // generated in-memory backref cache
+    #[serde(skip)]
+    crate_paths_index: FxHashMap<(ItemType, Vec<Symbol>), usize>,
+}
+
+impl SerializedSearchIndex {
+    fn load(doc_root: &Path, resource_suffix: &str) -> Result<SerializedSearchIndex, Error> {
+        let mut names: Vec<String> = Vec::new();
+        let mut path_data: Vec<Option<PathData>> = Vec::new();
+        let mut entry_data: Vec<Option<EntryData>> = Vec::new();
+        let mut descs: Vec<String> = Vec::new();
+        let mut function_data: Vec<Option<FunctionData>> = Vec::new();
+        let mut type_data: Vec<Option<TypeData>> = Vec::new();
+        let mut alias_pointers: Vec<Option<usize>> = Vec::new();
+
+        let mut generic_inverted_index: Vec<Vec<Vec<u32>>> = Vec::new();
+
+        match perform_read_strings(resource_suffix, doc_root, "name", &mut names) {
+            Ok(()) => {
+                perform_read_serde(resource_suffix, doc_root, "path", &mut path_data)?;
+                perform_read_serde(resource_suffix, doc_root, "entry", &mut entry_data)?;
+                perform_read_strings(resource_suffix, doc_root, "desc", &mut descs)?;
+                perform_read_serde(resource_suffix, doc_root, "function", &mut function_data)?;
+                perform_read_serde(resource_suffix, doc_root, "type", &mut type_data)?;
+                perform_read_serde(resource_suffix, doc_root, "alias", &mut alias_pointers)?;
+                perform_read_postings(
+                    resource_suffix,
+                    doc_root,
+                    "generic_inverted_index",
+                    &mut generic_inverted_index,
+                )?;
+            }
+            Err(_) => {
+                names.clear();
+            }
+        }
+        fn perform_read_strings(
+            resource_suffix: &str,
+            doc_root: &Path,
+            column_name: &str,
+            column: &mut Vec<String>,
+        ) -> Result<(), Error> {
+            let root_path = doc_root.join(format!("search.index/root{resource_suffix}.js"));
+            let column_path = doc_root.join(format!("search.index/{column_name}/"));
+            stringdex_internals::read_data_from_disk_column(
+                root_path,
+                column_name.as_bytes(),
+                column_path.clone(),
+                &mut |_id, item| {
+                    column.push(String::from_utf8(item.to_vec())?);
+                    Ok(())
+                },
+            )
+            .map_err(
+                |error: stringdex_internals::ReadDataError<Box<dyn std::error::Error>>| Error {
+                    file: column_path,
+                    error: format!("failed to read column from disk: {error}"),
+                },
+            )
+        }
+        fn perform_read_serde(
+            resource_suffix: &str,
+            doc_root: &Path,
+            column_name: &str,
+            column: &mut Vec<Option<impl for<'de> Deserialize<'de> + 'static>>,
+        ) -> Result<(), Error> {
+            let root_path = doc_root.join(format!("search.index/root{resource_suffix}.js"));
+            let column_path = doc_root.join(format!("search.index/{column_name}/"));
+            stringdex_internals::read_data_from_disk_column(
+                root_path,
+                column_name.as_bytes(),
+                column_path.clone(),
+                &mut |_id, item| {
+                    if item.is_empty() {
+                        column.push(None);
+                    } else {
+                        column.push(Some(serde_json::from_slice(item)?));
+                    }
+                    Ok(())
+                },
+            )
+            .map_err(
+                |error: stringdex_internals::ReadDataError<Box<dyn std::error::Error>>| Error {
+                    file: column_path,
+                    error: format!("failed to read column from disk: {error}"),
+                },
+            )
+        }
+        fn perform_read_postings(
+            resource_suffix: &str,
+            doc_root: &Path,
+            column_name: &str,
+            column: &mut Vec<Vec<Vec<u32>>>,
+        ) -> Result<(), Error> {
+            let root_path = doc_root.join(format!("search.index/root{resource_suffix}.js"));
+            let column_path = doc_root.join(format!("search.index/{column_name}/"));
+            stringdex_internals::read_data_from_disk_column(
+                root_path,
+                column_name.as_bytes(),
+                column_path.clone(),
+                &mut |_id, buf| {
+                    let mut postings = Vec::new();
+                    encode::read_postings_from_string(&mut postings, buf);
+                    column.push(postings);
+                    Ok(())
+                },
+            )
+            .map_err(
+                |error: stringdex_internals::ReadDataError<Box<dyn std::error::Error>>| Error {
+                    file: column_path,
+                    error: format!("failed to read column from disk: {error}"),
+                },
+            )
+        }
+
+        assert_eq!(names.len(), path_data.len());
+        assert_eq!(path_data.len(), entry_data.len());
+        assert_eq!(entry_data.len(), descs.len());
+        assert_eq!(descs.len(), function_data.len());
+        assert_eq!(function_data.len(), type_data.len());
+        assert_eq!(type_data.len(), alias_pointers.len());
+
+        // generic_inverted_index is not the same length as other columns,
+        // because it's actually a completely different set of objects
+
+        let mut crate_paths_index: FxHashMap<(ItemType, Vec<Symbol>), usize> = FxHashMap::default();
+        for (i, (name, path_data)) in names.iter().zip(path_data.iter()).enumerate() {
+            if let Some(path_data) = path_data {
+                let full_path = if path_data.module_path.is_empty() {
+                    vec![Symbol::intern(name)]
+                } else {
+                    let mut full_path = path_data.module_path.to_vec();
+                    full_path.push(Symbol::intern(name));
+                    full_path
+                };
+                crate_paths_index.insert((path_data.ty, full_path), i);
+            }
+        }
+
+        Ok(SerializedSearchIndex {
+            names,
+            path_data,
+            entry_data,
+            descs,
+            function_data,
+            type_data,
+            alias_pointers,
+            generic_inverted_index,
+            crate_paths_index,
+        })
+    }
+    fn push(
+        &mut self,
+        name: String,
+        path_data: Option<PathData>,
+        entry_data: Option<EntryData>,
+        desc: String,
+        function_data: Option<FunctionData>,
+        type_data: Option<TypeData>,
+        alias_pointer: Option<usize>,
+    ) -> usize {
+        let index = self.names.len();
+        assert_eq!(self.names.len(), self.path_data.len());
+        if let Some(path_data) = &path_data
+            && let name = Symbol::intern(&name)
+            && let fqp = if path_data.module_path.is_empty() {
+                vec![name]
+            } else {
+                let mut v = path_data.module_path.clone();
+                v.push(name);
+                v
+            }
+            && let Some(&other_path) = self.crate_paths_index.get(&(path_data.ty, fqp))
+            && self.path_data.get(other_path).map_or(false, Option::is_some)
+        {
+            self.path_data.push(None);
+        } else {
+            self.path_data.push(path_data);
+        }
+        self.names.push(name);
+        assert_eq!(self.entry_data.len(), self.descs.len());
+        self.entry_data.push(entry_data);
+        assert_eq!(self.descs.len(), self.function_data.len());
+        self.descs.push(desc);
+        assert_eq!(self.function_data.len(), self.type_data.len());
+        self.function_data.push(function_data);
+        assert_eq!(self.type_data.len(), self.alias_pointers.len());
+        self.type_data.push(type_data);
+        self.alias_pointers.push(alias_pointer);
+        index
+    }
+    fn push_path(&mut self, name: String, path_data: PathData) -> usize {
+        self.push(name, Some(path_data), None, String::new(), None, None, None)
+    }
+    fn push_type(&mut self, name: String, path_data: PathData, type_data: TypeData) -> usize {
+        self.push(name, Some(path_data), None, String::new(), None, Some(type_data), None)
+    }
+    fn push_alias(&mut self, name: String, alias_pointer: usize) -> usize {
+        self.push(name, None, None, String::new(), None, None, Some(alias_pointer))
+    }
+
+    fn get_id_by_module_path(&mut self, path: &[Symbol]) -> usize {
+        let ty = if path.len() == 1 { ItemType::ExternCrate } else { ItemType::Module };
+        match self.crate_paths_index.entry((ty, path.to_vec())) {
+            Entry::Occupied(index) => *index.get(),
+            Entry::Vacant(slot) => {
+                slot.insert(self.path_data.len());
+                let (name, module_path) = path.split_last().unwrap();
+                self.push_path(
+                    name.as_str().to_string(),
+                    PathData { ty, module_path: module_path.to_vec(), exact_module_path: None },
+                )
+            }
+        }
+    }
+
+    pub(crate) fn union(mut self, other: &SerializedSearchIndex) -> SerializedSearchIndex {
+        let other_entryid_offset = self.names.len();
+        let mut map_other_pathid_to_self_pathid: Vec<usize> = Vec::new();
+        let mut skips = FxHashSet::default();
+        for (other_pathid, other_path_data) in other.path_data.iter().enumerate() {
+            if let Some(other_path_data) = other_path_data {
+                let mut fqp = other_path_data.module_path.clone();
+                let name = Symbol::intern(&other.names[other_pathid]);
+                fqp.push(name);
+                let self_pathid = other_entryid_offset + other_pathid;
+                let self_pathid = match self.crate_paths_index.entry((other_path_data.ty, fqp)) {
+                    Entry::Vacant(slot) => {
+                        slot.insert(self_pathid);
+                        self_pathid
+                    }
+                    Entry::Occupied(existing_entryid) => {
+                        skips.insert(other_pathid);
+                        let self_pathid = *existing_entryid.get();
+                        let new_type_data = match (
+                            self.type_data[self_pathid].take(),
+                            other.type_data[other_pathid].as_ref(),
+                        ) {
+                            (Some(self_type_data), None) => Some(self_type_data),
+                            (None, Some(other_type_data)) => Some(TypeData {
+                                search_unbox: other_type_data.search_unbox,
+                                inverted_function_signature_index: other_type_data
+                                    .inverted_function_signature_index
+                                    .iter()
+                                    .cloned()
+                                    .map(|mut list: Vec<u32>| {
+                                        for fnid in &mut list {
+                                            assert!(
+                                                other.function_data
+                                                    [usize::try_from(*fnid).unwrap()]
+                                                .is_some(),
+                                            );
+                                            // this is valid because we call `self.push()` once, exactly, for every entry,
+                                            // even if we're just pushing a tombstone
+                                            *fnid += u32::try_from(other_entryid_offset).unwrap();
+                                        }
+                                        list
+                                    })
+                                    .collect(),
+                            }),
+                            (Some(mut self_type_data), Some(other_type_data)) => {
+                                for (size, other_list) in other_type_data
+                                    .inverted_function_signature_index
+                                    .iter()
+                                    .enumerate()
+                                {
+                                    while self_type_data.inverted_function_signature_index.len()
+                                        <= size
+                                    {
+                                        self_type_data
+                                            .inverted_function_signature_index
+                                            .push(Vec::new());
+                                    }
+                                    self_type_data.inverted_function_signature_index[size].extend(
+                                        other_list.iter().copied().map(|fnid| {
+                                            assert!(
+                                                other.function_data[usize::try_from(fnid).unwrap()]
+                                                    .is_some(),
+                                            );
+                                            // this is valid because we call `self.push()` once, exactly, for every entry,
+                                            // even if we're just pushing a tombstone
+                                            fnid + u32::try_from(other_entryid_offset).unwrap()
+                                        }),
+                                    )
+                                }
+                                Some(self_type_data)
+                            }
+                            (None, None) => None,
+                        };
+                        self.type_data[self_pathid] = new_type_data;
+                        self_pathid
+                    }
+                };
+                map_other_pathid_to_self_pathid.push(self_pathid);
+            } else {
+                // if this gets used, we want it to crash
+                // this should be impossible as a valid index, since some of the
+                // memory must be used for stuff other than the list
+                map_other_pathid_to_self_pathid.push(!0);
+            }
+        }
+        for other_entryid in 0..other.names.len() {
+            if skips.contains(&other_entryid) {
+                // we push tombstone entries to keep the IDs lined up
+                self.push(String::new(), None, None, String::new(), None, None, None);
+            } else {
+                self.push(
+                    other.names[other_entryid].clone(),
+                    other.path_data[other_entryid].clone(),
+                    other.entry_data[other_entryid].as_ref().map(|other_entry_data| EntryData {
+                        parent: other_entry_data
+                            .parent
+                            .map(|parent| map_other_pathid_to_self_pathid[parent])
+                            .clone(),
+                        module_path: other_entry_data
+                            .module_path
+                            .map(|path| map_other_pathid_to_self_pathid[path])
+                            .clone(),
+                        exact_module_path: other_entry_data
+                            .exact_module_path
+                            .map(|exact_path| map_other_pathid_to_self_pathid[exact_path])
+                            .clone(),
+                        krate: map_other_pathid_to_self_pathid[other_entry_data.krate],
+                        ..other_entry_data.clone()
+                    }),
+                    other.descs[other_entryid].clone(),
+                    other.function_data[other_entryid].as_ref().map(|function_data| FunctionData {
+                        function_signature: {
+                            let (mut func, _offset) =
+                                IndexItemFunctionType::read_from_string_without_param_names(
+                                    function_data.function_signature.as_bytes(),
+                                );
+                            fn map_fn_sig_item(
+                                map_other_pathid_to_self_pathid: &mut Vec<usize>,
+                                ty: &mut RenderType,
+                            ) {
+                                match ty.id {
+                                    None => {}
+                                    Some(RenderTypeId::Index(generic)) if generic < 0 => {}
+                                    Some(RenderTypeId::Index(id)) => {
+                                        let id = usize::try_from(id).unwrap();
+                                        let id = map_other_pathid_to_self_pathid[id];
+                                        assert!(id != !0);
+                                        ty.id =
+                                            Some(RenderTypeId::Index(isize::try_from(id).unwrap()));
+                                    }
+                                    _ => unreachable!(),
+                                }
+                                if let Some(generics) = &mut ty.generics {
+                                    for generic in generics {
+                                        map_fn_sig_item(map_other_pathid_to_self_pathid, generic);
+                                    }
+                                }
+                                if let Some(bindings) = &mut ty.bindings {
+                                    for (param, constraints) in bindings {
+                                        *param = match *param {
+                                            param @ RenderTypeId::Index(generic) if generic < 0 => {
+                                                param
+                                            }
+                                            RenderTypeId::Index(id) => {
+                                                let id = usize::try_from(id).unwrap();
+                                                let id = map_other_pathid_to_self_pathid[id];
+                                                assert!(id != !0);
+                                                RenderTypeId::Index(isize::try_from(id).unwrap())
+                                            }
+                                            _ => unreachable!(),
+                                        };
+                                        for constraint in constraints {
+                                            map_fn_sig_item(
+                                                map_other_pathid_to_self_pathid,
+                                                constraint,
+                                            );
+                                        }
+                                    }
+                                }
+                            }
+                            for input in &mut func.inputs {
+                                map_fn_sig_item(&mut map_other_pathid_to_self_pathid, input);
+                            }
+                            for output in &mut func.output {
+                                map_fn_sig_item(&mut map_other_pathid_to_self_pathid, output);
+                            }
+                            for clause in &mut func.where_clause {
+                                for entry in clause {
+                                    map_fn_sig_item(&mut map_other_pathid_to_self_pathid, entry);
+                                }
+                            }
+                            let mut result =
+                                String::with_capacity(function_data.function_signature.len());
+                            func.write_to_string_without_param_names(&mut result);
+                            result
+                        },
+                        param_names: function_data.param_names.clone(),
+                    }),
+                    other.type_data[other_entryid].as_ref().map(|type_data| TypeData {
+                        inverted_function_signature_index: type_data
+                            .inverted_function_signature_index
+                            .iter()
+                            .cloned()
+                            .map(|mut list| {
+                                for fnid in &mut list {
+                                    assert!(
+                                        other.function_data[usize::try_from(*fnid).unwrap()]
+                                            .is_some(),
+                                    );
+                                    // this is valid because we call `self.push()` once, exactly, for every entry,
+                                    // even if we're just pushing a tombstone
+                                    *fnid += u32::try_from(other_entryid_offset).unwrap();
+                                }
+                                list
+                            })
+                            .collect(),
+                        search_unbox: type_data.search_unbox,
+                    }),
+                    other.alias_pointers[other_entryid]
+                        .map(|alias_pointer| alias_pointer + other_entryid_offset),
+                );
+            }
+        }
+        for (i, other_generic_inverted_index) in other.generic_inverted_index.iter().enumerate() {
+            for (size, other_list) in other_generic_inverted_index.iter().enumerate() {
+                let self_generic_inverted_index = match self.generic_inverted_index.get_mut(i) {
+                    Some(self_generic_inverted_index) => self_generic_inverted_index,
+                    None => {
+                        self.generic_inverted_index.push(Vec::new());
+                        self.generic_inverted_index.last_mut().unwrap()
+                    }
+                };
+                while self_generic_inverted_index.len() <= size {
+                    self_generic_inverted_index.push(Vec::new());
+                }
+                self_generic_inverted_index[size].extend(
+                    other_list
+                        .iter()
+                        .copied()
+                        .map(|fnid| fnid + u32::try_from(other_entryid_offset).unwrap()),
+                );
+            }
+        }
+        self
+    }
+
+    pub(crate) fn sort(self) -> SerializedSearchIndex {
+        let mut idlist: Vec<usize> = (0..self.names.len()).collect();
+        // nameless entries are tombstones, and will be removed after sorting
+        // sort shorter names first, so that we can present them in order out of search.js
+        idlist.sort_by_key(|&id| {
+            (
+                self.names[id].is_empty(),
+                self.names[id].len(),
+                &self.names[id],
+                self.entry_data[id].as_ref().map_or("", |entry| self.names[entry.krate].as_str()),
+                self.path_data[id].as_ref().map_or(&[][..], |entry| &entry.module_path[..]),
+            )
+        });
+        let map = FxHashMap::from_iter(
+            idlist.iter().enumerate().map(|(new_id, &old_id)| (old_id, new_id)),
+        );
+        let mut new = SerializedSearchIndex::default();
+        for &id in &idlist {
+            if self.names[id].is_empty() {
+                break;
+            }
+            new.push(
+                self.names[id].clone(),
+                self.path_data[id].clone(),
+                self.entry_data[id].as_ref().map(
+                    |EntryData {
+                         krate,
+                         ty,
+                         module_path,
+                         exact_module_path,
+                         parent,
+                         deprecated,
+                         associated_item_disambiguator,
+                     }| EntryData {
+                        krate: *map.get(krate).unwrap(),
+                        ty: *ty,
+                        module_path: module_path.and_then(|path_id| map.get(&path_id).copied()),
+                        exact_module_path: exact_module_path
+                            .and_then(|path_id| map.get(&path_id).copied()),
+                        parent: parent.and_then(|path_id| map.get(&path_id).copied()),
+                        deprecated: *deprecated,
+                        associated_item_disambiguator: associated_item_disambiguator.clone(),
+                    },
+                ),
+                self.descs[id].clone(),
+                self.function_data[id].as_ref().map(
+                    |FunctionData { function_signature, param_names }| FunctionData {
+                        function_signature: {
+                            let (mut func, _offset) =
+                                IndexItemFunctionType::read_from_string_without_param_names(
+                                    function_signature.as_bytes(),
+                                );
+                            fn map_fn_sig_item(map: &FxHashMap<usize, usize>, ty: &mut RenderType) {
+                                match ty.id {
+                                    None => {}
+                                    Some(RenderTypeId::Index(generic)) if generic < 0 => {}
+                                    Some(RenderTypeId::Index(id)) => {
+                                        let id = usize::try_from(id).unwrap();
+                                        let id = *map.get(&id).unwrap();
+                                        assert!(id != !0);
+                                        ty.id =
+                                            Some(RenderTypeId::Index(isize::try_from(id).unwrap()));
+                                    }
+                                    _ => unreachable!(),
+                                }
+                                if let Some(generics) = &mut ty.generics {
+                                    for generic in generics {
+                                        map_fn_sig_item(map, generic);
+                                    }
+                                }
+                                if let Some(bindings) = &mut ty.bindings {
+                                    for (param, constraints) in bindings {
+                                        *param = match *param {
+                                            param @ RenderTypeId::Index(generic) if generic < 0 => {
+                                                param
+                                            }
+                                            RenderTypeId::Index(id) => {
+                                                let id = usize::try_from(id).unwrap();
+                                                let id = *map.get(&id).unwrap();
+                                                assert!(id != !0);
+                                                RenderTypeId::Index(isize::try_from(id).unwrap())
+                                            }
+                                            _ => unreachable!(),
+                                        };
+                                        for constraint in constraints {
+                                            map_fn_sig_item(map, constraint);
+                                        }
+                                    }
+                                }
+                            }
+                            for input in &mut func.inputs {
+                                map_fn_sig_item(&map, input);
+                            }
+                            for output in &mut func.output {
+                                map_fn_sig_item(&map, output);
+                            }
+                            for clause in &mut func.where_clause {
+                                for entry in clause {
+                                    map_fn_sig_item(&map, entry);
+                                }
+                            }
+                            let mut result = String::with_capacity(function_signature.len());
+                            func.write_to_string_without_param_names(&mut result);
+                            result
+                        },
+                        param_names: param_names.clone(),
+                    },
+                ),
+                self.type_data[id].as_ref().map(
+                    |TypeData { search_unbox, inverted_function_signature_index }| {
+                        let inverted_function_signature_index: Vec<Vec<u32>> =
+                            inverted_function_signature_index
+                                .iter()
+                                .cloned()
+                                .map(|mut list| {
+                                    for id in &mut list {
+                                        *id = u32::try_from(
+                                            *map.get(&usize::try_from(*id).unwrap()).unwrap(),
+                                        )
+                                        .unwrap();
+                                    }
+                                    list.sort();
+                                    list
+                                })
+                                .collect();
+                        TypeData { search_unbox: *search_unbox, inverted_function_signature_index }
+                    },
+                ),
+                self.alias_pointers[id].and_then(|alias| map.get(&alias).copied()),
+            );
+        }
+        new.generic_inverted_index = self
+            .generic_inverted_index
+            .into_iter()
+            .map(|mut postings| {
+                for list in postings.iter_mut() {
+                    let mut new_list: Vec<u32> = list
+                        .iter()
+                        .copied()
+                        .filter_map(|id| u32::try_from(*map.get(&usize::try_from(id).ok()?)?).ok())
+                        .collect();
+                    new_list.sort();
+                    *list = new_list;
+                }
+                postings
+            })
+            .collect();
+        new
+    }
+
+    pub(crate) fn write_to(self, doc_root: &Path, resource_suffix: &str) -> Result<(), Error> {
+        let SerializedSearchIndex {
+            names,
+            path_data,
+            entry_data,
+            descs,
+            function_data,
+            type_data,
+            alias_pointers,
+            generic_inverted_index,
+            crate_paths_index: _,
+        } = self;
+        let mut serialized_root = Vec::new();
+        serialized_root.extend_from_slice(br#"rr_('{"normalizedName":{"I":""#);
+        let normalized_names = names
+            .iter()
+            .map(|name| {
+                if name.contains("_") {
+                    name.replace("_", "").to_ascii_lowercase()
+                } else {
+                    name.to_ascii_lowercase()
+                }
+            })
+            .collect::<Vec<String>>();
+        let names_search_tree = stringdex_internals::tree::encode_search_tree_ukkonen(
+            normalized_names.iter().map(|name| name.as_bytes()),
+        );
+        let dir_path = doc_root.join(format!("search.index/"));
+        let _ = std::fs::remove_dir_all(&dir_path); // if already missing, no problem
+        stringdex_internals::write_tree_to_disk(
+            &names_search_tree,
+            &dir_path,
+            &mut serialized_root,
+        )
+        .map_err(|error| Error {
+            file: dir_path,
+            error: format!("failed to write name tree to disk: {error}"),
+        })?;
+        std::mem::drop(names_search_tree);
+        serialized_root.extend_from_slice(br#"","#);
+        serialized_root.extend_from_slice(&perform_write_strings(
+            doc_root,
+            "normalizedName",
+            normalized_names.into_iter(),
+        )?);
+        serialized_root.extend_from_slice(br#"},"crateNames":{"#);
+        let mut crates: Vec<&[u8]> = entry_data
+            .iter()
+            .filter_map(|entry_data| Some(names[entry_data.as_ref()?.krate].as_bytes()))
+            .collect();
+        crates.sort();
+        crates.dedup();
+        serialized_root.extend_from_slice(&perform_write_strings(
+            doc_root,
+            "crateNames",
+            crates.into_iter(),
+        )?);
+        serialized_root.extend_from_slice(br#"},"name":{"#);
+        serialized_root.extend_from_slice(&perform_write_strings(doc_root, "name", names.iter())?);
+        serialized_root.extend_from_slice(br#"},"path":{"#);
+        serialized_root.extend_from_slice(&perform_write_serde(doc_root, "path", path_data)?);
+        serialized_root.extend_from_slice(br#"},"entry":{"#);
+        serialized_root.extend_from_slice(&perform_write_serde(doc_root, "entry", entry_data)?);
+        serialized_root.extend_from_slice(br#"},"desc":{"#);
+        serialized_root.extend_from_slice(&perform_write_strings(
+            doc_root,
+            "desc",
+            descs.into_iter(),
+        )?);
+        serialized_root.extend_from_slice(br#"},"function":{"#);
+        serialized_root.extend_from_slice(&perform_write_serde(
+            doc_root,
+            "function",
+            function_data,
+        )?);
+        serialized_root.extend_from_slice(br#"},"type":{"#);
+        serialized_root.extend_from_slice(&perform_write_serde(doc_root, "type", type_data)?);
+        serialized_root.extend_from_slice(br#"},"alias":{"#);
+        serialized_root.extend_from_slice(&perform_write_serde(doc_root, "alias", alias_pointers)?);
+        serialized_root.extend_from_slice(br#"},"generic_inverted_index":{"#);
+        serialized_root.extend_from_slice(&perform_write_postings(
+            doc_root,
+            "generic_inverted_index",
+            generic_inverted_index,
+        )?);
+        serialized_root.extend_from_slice(br#"}}')"#);
+        fn perform_write_strings(
+            doc_root: &Path,
+            dirname: &str,
+            mut column: impl Iterator<Item = impl AsRef<[u8]> + Clone> + ExactSizeIterator,
+        ) -> Result<Vec<u8>, Error> {
+            let dir_path = doc_root.join(format!("search.index/{dirname}"));
+            stringdex_internals::write_data_to_disk(&mut column, &dir_path).map_err(|error| Error {
+                file: dir_path,
+                error: format!("failed to write column to disk: {error}"),
+            })
+        }
+        fn perform_write_serde(
+            doc_root: &Path,
+            dirname: &str,
+            column: Vec<Option<impl Serialize>>,
+        ) -> Result<Vec<u8>, Error> {
+            perform_write_strings(
+                doc_root,
+                dirname,
+                column.into_iter().map(|value| {
+                    if let Some(value) = value {
+                        serde_json::to_vec(&value).unwrap()
+                    } else {
+                        Vec::new()
+                    }
+                }),
+            )
+        }
+        fn perform_write_postings(
+            doc_root: &Path,
+            dirname: &str,
+            column: Vec<Vec<Vec<u32>>>,
+        ) -> Result<Vec<u8>, Error> {
+            perform_write_strings(
+                doc_root,
+                dirname,
+                column.into_iter().map(|postings| {
+                    let mut buf = Vec::new();
+                    encode::write_postings_to_string(&postings, &mut buf);
+                    buf
+                }),
+            )
+        }
+        std::fs::write(
+            doc_root.join(format!("search.index/root{resource_suffix}.js")),
+            serialized_root,
+        )
+        .map_err(|error| Error {
+            file: doc_root.join(format!("search.index/root{resource_suffix}.js")),
+            error: format!("failed to write root to disk: {error}"),
+        })?;
+        Ok(())
+    }
+}
+
+#[derive(Clone, Debug)]
+struct EntryData {
+    krate: usize,
+    ty: ItemType,
+    module_path: Option<usize>,
+    exact_module_path: Option<usize>,
+    parent: Option<usize>,
+    deprecated: bool,
+    associated_item_disambiguator: Option<String>,
+}
+
+impl Serialize for EntryData {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut seq = serializer.serialize_seq(None)?;
+        seq.serialize_element(&self.krate)?;
+        seq.serialize_element(&self.ty)?;
+        seq.serialize_element(&self.module_path.map(|id| id + 1).unwrap_or(0))?;
+        seq.serialize_element(&self.exact_module_path.map(|id| id + 1).unwrap_or(0))?;
+        seq.serialize_element(&self.parent.map(|id| id + 1).unwrap_or(0))?;
+        seq.serialize_element(&if self.deprecated { 1 } else { 0 })?;
+        if let Some(disambig) = &self.associated_item_disambiguator {
+            seq.serialize_element(&disambig)?;
+        }
+        seq.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for EntryData {
+    fn deserialize<D>(deserializer: D) -> Result<EntryData, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct EntryDataVisitor;
+        impl<'de> de::Visitor<'de> for EntryDataVisitor {
+            type Value = EntryData;
+            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(formatter, "path data")
+            }
+            fn visit_seq<A: de::SeqAccess<'de>>(self, mut v: A) -> Result<EntryData, A::Error> {
+                let krate: usize =
+                    v.next_element()?.ok_or_else(|| A::Error::missing_field("krate"))?;
+                let ty: ItemType =
+                    v.next_element()?.ok_or_else(|| A::Error::missing_field("ty"))?;
+                let module_path: SerializedOptional32 =
+                    v.next_element()?.ok_or_else(|| A::Error::missing_field("module_path"))?;
+                let exact_module_path: SerializedOptional32 = v
+                    .next_element()?
+                    .ok_or_else(|| A::Error::missing_field("exact_module_path"))?;
+                let parent: SerializedOptional32 =
+                    v.next_element()?.ok_or_else(|| A::Error::missing_field("parent"))?;
+                let deprecated: u32 = v.next_element()?.unwrap_or(0);
+                let associated_item_disambiguator: Option<String> = v.next_element()?;
+                Ok(EntryData {
+                    krate,
+                    ty,
+                    module_path: Option::<i32>::from(module_path).map(|path| path as usize),
+                    exact_module_path: Option::<i32>::from(exact_module_path)
+                        .map(|path| path as usize),
+                    parent: Option::<i32>::from(parent).map(|path| path as usize),
+                    deprecated: deprecated != 0,
+                    associated_item_disambiguator,
+                })
+            }
+        }
+        deserializer.deserialize_any(EntryDataVisitor)
+    }
+}
+
+#[derive(Clone, Debug)]
+struct PathData {
+    ty: ItemType,
+    module_path: Vec<Symbol>,
+    exact_module_path: Option<Vec<Symbol>>,
 }
 
-const DESC_INDEX_SHARD_LEN: usize = 128 * 1024;
+impl Serialize for PathData {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut seq = serializer.serialize_seq(None)?;
+        seq.serialize_element(&self.ty)?;
+        seq.serialize_element(&if self.module_path.is_empty() {
+            String::new()
+        } else {
+            join_path_syms(&self.module_path)
+        })?;
+        if let Some(ref path) = self.exact_module_path {
+            seq.serialize_element(&if path.is_empty() {
+                String::new()
+            } else {
+                join_path_syms(path)
+            })?;
+        }
+        seq.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for PathData {
+    fn deserialize<D>(deserializer: D) -> Result<PathData, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct PathDataVisitor;
+        impl<'de> de::Visitor<'de> for PathDataVisitor {
+            type Value = PathData;
+            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(formatter, "path data")
+            }
+            fn visit_seq<A: de::SeqAccess<'de>>(self, mut v: A) -> Result<PathData, A::Error> {
+                let ty: ItemType =
+                    v.next_element()?.ok_or_else(|| A::Error::missing_field("ty"))?;
+                let module_path: String =
+                    v.next_element()?.ok_or_else(|| A::Error::missing_field("module_path"))?;
+                let exact_module_path: Option<String> =
+                    v.next_element()?.and_then(SerializedOptionalString::into);
+                Ok(PathData {
+                    ty,
+                    module_path: if module_path.is_empty() {
+                        vec![]
+                    } else {
+                        module_path.split("::").map(Symbol::intern).collect()
+                    },
+                    exact_module_path: exact_module_path.map(|path| {
+                        if path.is_empty() {
+                            vec![]
+                        } else {
+                            path.split("::").map(Symbol::intern).collect()
+                        }
+                    }),
+                })
+            }
+        }
+        deserializer.deserialize_any(PathDataVisitor)
+    }
+}
+
+#[derive(Clone, Debug)]
+struct TypeData {
+    /// If set to "true", the generics can be matched without having to
+    /// mention the type itself. The truth table, assuming `Unboxable`
+    /// has `search_unbox = true` and `Inner` has `search_unbox = false`
+    ///
+    /// | **query**          | `Unboxable<Inner>` | `Inner` | `Inner<Unboxable>` |
+    /// |--------------------|--------------------|---------|--------------------|
+    /// | `Inner`            | yes                | yes     | yes                |
+    /// | `Unboxable`        | yes                | no      | no                 |
+    /// | `Unboxable<Inner>` | yes                | no      | no                 |
+    /// | `Inner<Unboxable>` | no                 | no      | yes                |
+    search_unbox: bool,
+    /// List of functions that mention this type in their type signature.
+    ///
+    /// - The outermost list has one entry per alpha-normalized generic.
+    ///
+    /// - The second layer is sorted by number of types that appear in the
+    ///   type signature. The search engine iterates over these in order from
+    ///   smallest to largest. Functions with less stuff in their type
+    ///   signature are more likely to be what the user wants, because we never
+    ///   show functions that are *missing* parts of the query, so removing..
+    ///
+    /// - The final layer is the list of functions.
+    inverted_function_signature_index: Vec<Vec<u32>>,
+}
+
+impl Serialize for TypeData {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        if self.search_unbox || !self.inverted_function_signature_index.is_empty() {
+            let mut seq = serializer.serialize_seq(None)?;
+            if !self.inverted_function_signature_index.is_empty() {
+                let mut buf = Vec::new();
+                encode::write_postings_to_string(&self.inverted_function_signature_index, &mut buf);
+                let mut serialized_result = Vec::new();
+                stringdex_internals::encode::write_base64_to_bytes(&buf, &mut serialized_result);
+                seq.serialize_element(&String::from_utf8(serialized_result).unwrap())?;
+            }
+            if self.search_unbox {
+                seq.serialize_element(&1)?;
+            }
+            seq.end()
+        } else {
+            None::<()>.serialize(serializer)
+        }
+    }
+}
+
+impl<'de> Deserialize<'de> for TypeData {
+    fn deserialize<D>(deserializer: D) -> Result<TypeData, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct TypeDataVisitor;
+        impl<'de> de::Visitor<'de> for TypeDataVisitor {
+            type Value = TypeData;
+            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(formatter, "type data")
+            }
+            fn visit_none<E>(self) -> Result<TypeData, E> {
+                Ok(TypeData { inverted_function_signature_index: vec![], search_unbox: false })
+            }
+            fn visit_seq<A: de::SeqAccess<'de>>(self, mut v: A) -> Result<TypeData, A::Error> {
+                let inverted_function_signature_index: String =
+                    v.next_element()?.unwrap_or(String::new());
+                let search_unbox: u32 = v.next_element()?.unwrap_or(0);
+                let mut idx: Vec<u8> = Vec::new();
+                stringdex_internals::decode::read_base64_from_bytes(
+                    inverted_function_signature_index.as_bytes(),
+                    &mut idx,
+                )
+                .unwrap();
+                let mut inverted_function_signature_index = Vec::new();
+                encode::read_postings_from_string(&mut inverted_function_signature_index, &idx);
+                Ok(TypeData { inverted_function_signature_index, search_unbox: search_unbox == 1 })
+            }
+        }
+        deserializer.deserialize_any(TypeDataVisitor)
+    }
+}
+
+enum SerializedOptionalString {
+    None,
+    Some(String),
+}
+
+impl From<SerializedOptionalString> for Option<String> {
+    fn from(me: SerializedOptionalString) -> Option<String> {
+        match me {
+            SerializedOptionalString::Some(string) => Some(string),
+            SerializedOptionalString::None => None,
+        }
+    }
+}
+
+impl Serialize for SerializedOptionalString {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match self {
+            SerializedOptionalString::Some(string) => string.serialize(serializer),
+            SerializedOptionalString::None => 0.serialize(serializer),
+        }
+    }
+}
+impl<'de> Deserialize<'de> for SerializedOptionalString {
+    fn deserialize<D>(deserializer: D) -> Result<SerializedOptionalString, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct SerializedOptionalStringVisitor;
+        impl<'de> de::Visitor<'de> for SerializedOptionalStringVisitor {
+            type Value = SerializedOptionalString;
+            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(formatter, "0 or string")
+            }
+            fn visit_u64<E: de::Error>(self, v: u64) -> Result<SerializedOptionalString, E> {
+                if v != 0 {
+                    return Err(E::missing_field("not 0"));
+                }
+                Ok(SerializedOptionalString::None)
+            }
+            fn visit_string<E: de::Error>(self, v: String) -> Result<SerializedOptionalString, E> {
+                Ok(SerializedOptionalString::Some(v))
+            }
+            fn visit_str<E: de::Error>(self, v: &str) -> Result<SerializedOptionalString, E> {
+                Ok(SerializedOptionalString::Some(v.to_string()))
+            }
+        }
+        deserializer.deserialize_any(SerializedOptionalStringVisitor)
+    }
+}
+
+enum SerializedOptional32 {
+    None,
+    Some(i32),
+}
+
+impl From<SerializedOptional32> for Option<i32> {
+    fn from(me: SerializedOptional32) -> Option<i32> {
+        match me {
+            SerializedOptional32::Some(number) => Some(number),
+            SerializedOptional32::None => None,
+        }
+    }
+}
+
+impl Serialize for SerializedOptional32 {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match self {
+            &SerializedOptional32::Some(number) if number < 0 => number.serialize(serializer),
+            &SerializedOptional32::Some(number) => (number + 1).serialize(serializer),
+            &SerializedOptional32::None => 0.serialize(serializer),
+        }
+    }
+}
+impl<'de> Deserialize<'de> for SerializedOptional32 {
+    fn deserialize<D>(deserializer: D) -> Result<SerializedOptional32, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct SerializedOptional32Visitor;
+        impl<'de> de::Visitor<'de> for SerializedOptional32Visitor {
+            type Value = SerializedOptional32;
+            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(formatter, "integer")
+            }
+            fn visit_i64<E: de::Error>(self, v: i64) -> Result<SerializedOptional32, E> {
+                Ok(match v {
+                    0 => SerializedOptional32::None,
+                    v if v < 0 => SerializedOptional32::Some(v as i32),
+                    v => SerializedOptional32::Some(v as i32 - 1),
+                })
+            }
+            fn visit_u64<E: de::Error>(self, v: u64) -> Result<SerializedOptional32, E> {
+                Ok(match v {
+                    0 => SerializedOptional32::None,
+                    v => SerializedOptional32::Some(v as i32 - 1),
+                })
+            }
+        }
+        deserializer.deserialize_any(SerializedOptional32Visitor)
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct FunctionData {
+    function_signature: String,
+    param_names: Vec<String>,
+}
+
+impl Serialize for FunctionData {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut seq = serializer.serialize_seq(None)?;
+        seq.serialize_element(&self.function_signature)?;
+        seq.serialize_element(&self.param_names)?;
+        seq.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for FunctionData {
+    fn deserialize<D>(deserializer: D) -> Result<FunctionData, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct FunctionDataVisitor;
+        impl<'de> de::Visitor<'de> for FunctionDataVisitor {
+            type Value = FunctionData;
+            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(formatter, "fn data")
+            }
+            fn visit_seq<A: de::SeqAccess<'de>>(self, mut v: A) -> Result<FunctionData, A::Error> {
+                let function_signature: String = v
+                    .next_element()?
+                    .ok_or_else(|| A::Error::missing_field("function_signature"))?;
+                let param_names: Vec<String> =
+                    v.next_element()?.ok_or_else(|| A::Error::missing_field("param_names"))?;
+                Ok(FunctionData { function_signature, param_names })
+            }
+        }
+        deserializer.deserialize_any(FunctionDataVisitor)
+    }
+}
 
 /// Builds the search index from the collected metadata
 pub(crate) fn build_index(
     krate: &clean::Crate,
     cache: &mut Cache,
     tcx: TyCtxt<'_>,
-) -> SerializedSearchIndex {
-    // Maps from ID to position in the `crate_paths` array.
-    let mut itemid_to_pathid = FxHashMap::default();
-    let mut primitives = FxHashMap::default();
-    let mut associated_types = FxHashMap::default();
-
-    // item type, display path, re-exported internal path
-    let mut crate_paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)> = vec![];
+    doc_root: &Path,
+    resource_suffix: &str,
+) -> Result<SerializedSearchIndex, Error> {
+    let mut search_index = std::mem::take(&mut cache.search_index);
 
     // Attach all orphan items to the type's definition if the type
     // has since been learned.
@@ -74,15 +1171,15 @@ pub(crate) fn build_index(
     {
         if let Some((fqp, _)) = cache.paths.get(&parent) {
             let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache));
-            cache.search_index.push(IndexItem {
+            search_index.push(IndexItem {
                 ty: item.type_(),
                 defid: item.item_id.as_def_id(),
                 name: item.name.unwrap(),
-                path: join_path_syms(&fqp[..fqp.len() - 1]),
+                module_path: fqp[..fqp.len() - 1].to_vec(),
                 desc,
                 parent: Some(parent),
                 parent_idx: None,
-                exact_path: None,
+                exact_module_path: None,
                 impl_id,
                 search_type: get_function_type_for_search(
                     item,
@@ -97,85 +1194,299 @@ pub(crate) fn build_index(
         }
     }
 
+    // Sort search index items. This improves the compressibility of the search index.
+    search_index.sort_unstable_by(|k1, k2| {
+        // `sort_unstable_by_key` produces lifetime errors
+        // HACK(rustdoc): should not be sorting `CrateNum` or `DefIndex`, this will soon go away, too
+        let k1 =
+            (&k1.module_path, k1.name.as_str(), &k1.ty, k1.parent.map(|id| (id.index, id.krate)));
+        let k2 =
+            (&k2.module_path, k2.name.as_str(), &k2.ty, k2.parent.map(|id| (id.index, id.krate)));
+        Ord::cmp(&k1, &k2)
+    });
+
+    // Now, convert to an on-disk search index format
+    //
+    // if there's already a search index, load it into memory and add the new entries to it
+    // otherwise, do nothing
+    let mut serialized_index = SerializedSearchIndex::load(doc_root, resource_suffix)?;
+
+    // The crate always goes first in this list
+    let crate_name = krate.name(tcx);
     let crate_doc =
         short_markdown_summary(&krate.module.doc_value(), &krate.module.link_names(cache));
+    let crate_idx = {
+        let crate_path = (ItemType::ExternCrate, vec![crate_name]);
+        match serialized_index.crate_paths_index.entry(crate_path) {
+            Entry::Occupied(index) => {
+                let index = *index.get();
+                serialized_index.descs[index] = crate_doc;
+                for type_data in serialized_index.type_data.iter_mut() {
+                    if let Some(TypeData { inverted_function_signature_index, .. }) = type_data {
+                        for list in &mut inverted_function_signature_index[..] {
+                            list.retain(|fnid| {
+                                serialized_index.entry_data[usize::try_from(*fnid).unwrap()]
+                                    .as_ref()
+                                    .unwrap()
+                                    .krate
+                                    != index
+                            });
+                        }
+                    }
+                }
+                for i in (index + 1)..serialized_index.entry_data.len() {
+                    // if this crate has been built before, replace its stuff with new
+                    if let Some(EntryData { krate, .. }) = serialized_index.entry_data[i]
+                        && krate == index
+                    {
+                        serialized_index.entry_data[i] = None;
+                        serialized_index.descs[i] = String::new();
+                        serialized_index.function_data[i] = None;
+                        if serialized_index.path_data[i].is_none() {
+                            serialized_index.names[i] = String::new();
+                        }
+                    }
+                    if let Some(alias_pointer) = serialized_index.alias_pointers[i]
+                        && serialized_index.entry_data[alias_pointer].is_none()
+                    {
+                        serialized_index.alias_pointers[i] = None;
+                        if serialized_index.path_data[i].is_none()
+                            && serialized_index.entry_data[i].is_none()
+                        {
+                            serialized_index.names[i] = String::new();
+                        }
+                    }
+                }
+                index
+            }
+            Entry::Vacant(slot) => {
+                let krate = serialized_index.names.len();
+                slot.insert(krate);
+                serialized_index.push(
+                    crate_name.as_str().to_string(),
+                    Some(PathData {
+                        ty: ItemType::ExternCrate,
+                        module_path: vec![],
+                        exact_module_path: None,
+                    }),
+                    Some(EntryData {
+                        krate,
+                        ty: ItemType::ExternCrate,
+                        module_path: None,
+                        exact_module_path: None,
+                        parent: None,
+                        deprecated: false,
+                        associated_item_disambiguator: None,
+                    }),
+                    crate_doc,
+                    None,
+                    None,
+                    None,
+                );
+                krate
+            }
+        }
+    };
+
+    // First, populate associated item parents
+    let crate_items: Vec<&mut IndexItem> = search_index
+        .iter_mut()
+        .map(|item| {
+            item.parent_idx = item.parent.and_then(|defid| {
+                cache.paths.get(&defid).map(|&(ref fqp, ty)| {
+                    let pathid = serialized_index.names.len();
+                    match serialized_index.crate_paths_index.entry((ty, fqp.clone())) {
+                        Entry::Occupied(entry) => *entry.get(),
+                        Entry::Vacant(entry) => {
+                            entry.insert(pathid);
+                            let (name, path) = fqp.split_last().unwrap();
+                            serialized_index.push_path(
+                                name.as_str().to_string(),
+                                PathData {
+                                    ty,
+                                    module_path: path.to_vec(),
+                                    exact_module_path: if let Some(exact_path) =
+                                        cache.exact_paths.get(&defid)
+                                        && let Some((name2, exact_path)) = exact_path.split_last()
+                                        && name == name2
+                                    {
+                                        Some(exact_path.to_vec())
+                                    } else {
+                                        None
+                                    },
+                                },
+                            );
+                            usize::try_from(pathid).unwrap()
+                        }
+                    }
+                })
+            });
+
+            if let Some(defid) = item.defid
+                && item.parent_idx.is_none()
+            {
+                // If this is a re-export, retain the original path.
+                // Associated items don't use this.
+                // Their parent carries the exact fqp instead.
+                let exact_fqp = cache
+                    .exact_paths
+                    .get(&defid)
+                    .or_else(|| cache.external_paths.get(&defid).map(|(fqp, _)| fqp));
+                item.exact_module_path = exact_fqp.and_then(|fqp| {
+                    // Re-exports only count if the name is exactly the same.
+                    // This is a size optimization, since it means we only need
+                    // to store the name once (and the path is re-used for everything
+                    // exported from this same module). It's also likely to Do
+                    // What I Mean, since if a re-export changes the name, it might
+                    // also be a change in semantic meaning.
+                    if fqp.last() != Some(&item.name) {
+                        return None;
+                    }
+                    let path =
+                        if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) {
+                            // `#[macro_export]` always exports to the crate root.
+                            vec![tcx.crate_name(defid.krate)]
+                        } else {
+                            if fqp.len() < 2 {
+                                return None;
+                            }
+                            fqp[..fqp.len() - 1].to_vec()
+                        };
+                    if path == item.module_path {
+                        return None;
+                    }
+                    Some(path)
+                });
+            } else if let Some(parent_idx) = item.parent_idx {
+                let i = usize::try_from(parent_idx).unwrap();
+                item.module_path =
+                    serialized_index.path_data[i].as_ref().unwrap().module_path.clone();
+                item.exact_module_path =
+                    serialized_index.path_data[i].as_ref().unwrap().exact_module_path.clone();
+            }
 
-    #[derive(Eq, Ord, PartialEq, PartialOrd)]
-    struct SerSymbolAsStr(Symbol);
+            &mut *item
+        })
+        .collect();
 
-    impl Serialize for SerSymbolAsStr {
-        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-        where
-            S: Serializer,
+    // Now, find anywhere that the same name is used for two different items
+    // these need a disambiguator hash for lints
+    let mut associated_item_duplicates = FxHashMap::<(usize, ItemType, Symbol), usize>::default();
+    for item in crate_items.iter().map(|x| &*x) {
+        if item.impl_id.is_some()
+            && let Some(parent_idx) = item.parent_idx
         {
-            self.0.as_str().serialize(serializer)
+            let count =
+                associated_item_duplicates.entry((parent_idx, item.ty, item.name)).or_insert(0);
+            *count += 1;
         }
     }
 
-    type AliasMap = BTreeMap<SerSymbolAsStr, Vec<usize>>;
-    // Aliases added through `#[doc(alias = "...")]`. Since a few items can have the same alias,
-    // we need the alias element to have an array of items.
-    let mut aliases: AliasMap = BTreeMap::new();
+    // now populate the actual entries, type data, and function data
+    for item in crate_items {
+        assert_eq!(
+            item.parent.is_some(),
+            item.parent_idx.is_some(),
+            "`{}` is missing idx",
+            item.name
+        );
 
-    // Sort search index items. This improves the compressibility of the search index.
-    cache.search_index.sort_unstable_by(|k1, k2| {
-        // `sort_unstable_by_key` produces lifetime errors
-        // HACK(rustdoc): should not be sorting `CrateNum` or `DefIndex`, this will soon go away, too
-        let k1 = (&k1.path, k1.name.as_str(), &k1.ty, k1.parent.map(|id| (id.index, id.krate)));
-        let k2 = (&k2.path, k2.name.as_str(), &k2.ty, k2.parent.map(|id| (id.index, id.krate)));
-        Ord::cmp(&k1, &k2)
-    });
+        let module_path = Some(serialized_index.get_id_by_module_path(&item.module_path));
+        let exact_module_path = item
+            .exact_module_path
+            .as_ref()
+            .map(|path| serialized_index.get_id_by_module_path(path));
+
+        let new_entry_id = serialized_index.push(
+            item.name.as_str().to_string(),
+            None,
+            Some(EntryData {
+                ty: item.ty,
+                parent: item.parent_idx,
+                module_path,
+                exact_module_path,
+                deprecated: item.deprecation.is_some(),
+                associated_item_disambiguator: if let Some(impl_id) = item.impl_id
+                    && let Some(parent_idx) = item.parent_idx
+                    && associated_item_duplicates
+                        .get(&(parent_idx, item.ty, item.name))
+                        .copied()
+                        .unwrap_or(0)
+                        > 1
+                {
+                    Some(render::get_id_for_impl(tcx, ItemId::DefId(impl_id)))
+                } else {
+                    None
+                },
+                krate: crate_idx,
+            }),
+            item.desc.to_string(),
+            None, // filled in after all the types have been indexed
+            None,
+            None,
+        );
 
-    // Set up alias indexes.
-    for (i, item) in cache.search_index.iter().enumerate() {
+        // Aliases
+        // -------
         for alias in &item.aliases[..] {
-            aliases.entry(SerSymbolAsStr(*alias)).or_default().push(i);
+            serialized_index.push_alias(alias.as_str().to_string(), new_entry_id);
         }
-    }
-
-    // Reduce `DefId` in paths into smaller sequential numbers,
-    // and prune the paths that do not appear in the index.
-    let mut lastpath = "";
-    let mut lastpathid = 0isize;
 
-    // First, on function signatures
-    let mut search_index = std::mem::take(&mut cache.search_index);
-    for item in search_index.iter_mut() {
-        fn insert_into_map<F: std::hash::Hash + Eq>(
-            map: &mut FxHashMap<F, isize>,
-            itemid: F,
-            lastpathid: &mut isize,
-            crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
-            item_type: ItemType,
+        // Function signature reverse index
+        // --------------------------------
+        fn insert_into_map(
+            ty: ItemType,
             path: &[Symbol],
             exact_path: Option<&[Symbol]>,
             search_unbox: bool,
+            serialized_index: &mut SerializedSearchIndex,
+            used_in_function_signature: &mut BTreeSet<isize>,
         ) -> RenderTypeId {
-            match map.entry(itemid) {
-                Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
+            let pathid = serialized_index.names.len();
+            let pathid = match serialized_index.crate_paths_index.entry((ty, path.to_vec())) {
+                Entry::Occupied(entry) => {
+                    let id = *entry.get();
+                    if serialized_index.type_data[id].as_mut().is_none() {
+                        serialized_index.type_data[id] = Some(TypeData {
+                            search_unbox,
+                            inverted_function_signature_index: Vec::new(),
+                        });
+                    } else if search_unbox {
+                        serialized_index.type_data[id].as_mut().unwrap().search_unbox = true;
+                    }
+                    id
+                }
                 Entry::Vacant(entry) => {
-                    let pathid = *lastpathid;
                     entry.insert(pathid);
-                    *lastpathid += 1;
-                    crate_paths.push((
-                        item_type,
-                        path.to_vec(),
-                        exact_path.map(|path| path.to_vec()),
-                        search_unbox,
-                    ));
-                    RenderTypeId::Index(pathid)
+                    let (name, path) = path.split_last().unwrap();
+                    serialized_index.push_type(
+                        name.to_string(),
+                        PathData {
+                            ty,
+                            module_path: path.to_vec(),
+                            exact_module_path: if let Some(exact_path) = exact_path
+                                && let Some((name2, exact_path)) = exact_path.split_last()
+                                && name == name2
+                            {
+                                Some(exact_path.to_vec())
+                            } else {
+                                None
+                            },
+                        },
+                        TypeData { search_unbox, inverted_function_signature_index: Vec::new() },
+                    );
+                    pathid
                 }
-            }
+            };
+            used_in_function_signature.insert(isize::try_from(pathid).unwrap());
+            RenderTypeId::Index(isize::try_from(pathid).unwrap())
         }
 
         fn convert_render_type_id(
             id: RenderTypeId,
             cache: &mut Cache,
-            itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
-            primitives: &mut FxHashMap<Symbol, isize>,
-            associated_types: &mut FxHashMap<Symbol, isize>,
-            lastpathid: &mut isize,
-            crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
+            serialized_index: &mut SerializedSearchIndex,
+            used_in_function_signature: &mut BTreeSet<isize>,
             tcx: TyCtxt<'_>,
         ) -> Option<RenderTypeId> {
             use crate::clean::PrimitiveType;
@@ -183,7 +1494,9 @@ pub(crate) fn build_index(
             let search_unbox = match id {
                 RenderTypeId::Mut => false,
                 RenderTypeId::DefId(defid) => utils::has_doc_flag(tcx, defid, sym::search_unbox),
-                RenderTypeId::Primitive(PrimitiveType::Reference | PrimitiveType::Tuple) => true,
+                RenderTypeId::Primitive(
+                    PrimitiveType::Reference | PrimitiveType::RawPointer | PrimitiveType::Tuple,
+                ) => true,
                 RenderTypeId::Primitive(..) => false,
                 RenderTypeId::AssociatedType(..) => false,
                 // this bool is only used by `insert_into_map`, so it doesn't matter what we set here
@@ -192,39 +1505,55 @@ pub(crate) fn build_index(
             };
             match id {
                 RenderTypeId::Mut => Some(insert_into_map(
-                    primitives,
-                    kw::Mut,
-                    lastpathid,
-                    crate_paths,
                     ItemType::Keyword,
                     &[kw::Mut],
                     None,
                     search_unbox,
+                    serialized_index,
+                    used_in_function_signature,
                 )),
                 RenderTypeId::DefId(defid) => {
                     if let Some(&(ref fqp, item_type)) =
                         paths.get(&defid).or_else(|| external_paths.get(&defid))
                     {
-                        let exact_fqp = exact_paths
-                            .get(&defid)
-                            .or_else(|| external_paths.get(&defid).map(|(fqp, _)| fqp))
-                            // Re-exports only count if the name is exactly the same.
-                            // This is a size optimization, since it means we only need
-                            // to store the name once (and the path is re-used for everything
-                            // exported from this same module). It's also likely to Do
-                            // What I Mean, since if a re-export changes the name, it might
-                            // also be a change in semantic meaning.
-                            .filter(|this_fqp| this_fqp.last() == fqp.last());
-                        Some(insert_into_map(
-                            itemid_to_pathid,
-                            ItemId::DefId(defid),
-                            lastpathid,
-                            crate_paths,
-                            item_type,
-                            fqp,
-                            exact_fqp.map(|x| &x[..]).filter(|exact_fqp| exact_fqp != fqp),
-                            search_unbox,
-                        ))
+                        if tcx.lang_items().fn_mut_trait() == Some(defid)
+                            || tcx.lang_items().fn_once_trait() == Some(defid)
+                            || tcx.lang_items().fn_trait() == Some(defid)
+                        {
+                            let name = *fqp.last().unwrap();
+                            // Make absolutely sure we use this single, correct path,
+                            // because search.js needs to match. If we don't do this,
+                            // there are three different paths that these traits may
+                            // appear to come from.
+                            Some(insert_into_map(
+                                item_type,
+                                &[sym::core, sym::ops, name],
+                                Some(&[sym::core, sym::ops, name]),
+                                search_unbox,
+                                serialized_index,
+                                used_in_function_signature,
+                            ))
+                        } else {
+                            let exact_fqp = exact_paths
+                                .get(&defid)
+                                .or_else(|| external_paths.get(&defid).map(|(fqp, _)| fqp))
+                                .map(|v| &v[..])
+                                // Re-exports only count if the name is exactly the same.
+                                // This is a size optimization, since it means we only need
+                                // to store the name once (and the path is re-used for everything
+                                // exported from this same module). It's also likely to Do
+                                // What I Mean, since if a re-export changes the name, it might
+                                // also be a change in semantic meaning.
+                                .filter(|this_fqp| this_fqp.last() == fqp.last());
+                            Some(insert_into_map(
+                                item_type,
+                                fqp,
+                                exact_fqp,
+                                search_unbox,
+                                serialized_index,
+                                used_in_function_signature,
+                            ))
+                        }
                     } else {
                         None
                     }
@@ -232,26 +1561,25 @@ pub(crate) fn build_index(
                 RenderTypeId::Primitive(primitive) => {
                     let sym = primitive.as_sym();
                     Some(insert_into_map(
-                        primitives,
-                        sym,
-                        lastpathid,
-                        crate_paths,
                         ItemType::Primitive,
                         &[sym],
                         None,
                         search_unbox,
+                        serialized_index,
+                        used_in_function_signature,
                     ))
                 }
-                RenderTypeId::Index(_) => Some(id),
+                RenderTypeId::Index(index) => {
+                    used_in_function_signature.insert(index);
+                    Some(id)
+                }
                 RenderTypeId::AssociatedType(sym) => Some(insert_into_map(
-                    associated_types,
-                    sym,
-                    lastpathid,
-                    crate_paths,
                     ItemType::AssocType,
                     &[sym],
                     None,
                     search_unbox,
+                    serialized_index,
+                    used_in_function_signature,
                 )),
             }
         }
@@ -259,11 +1587,8 @@ pub(crate) fn build_index(
         fn convert_render_type(
             ty: &mut RenderType,
             cache: &mut Cache,
-            itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
-            primitives: &mut FxHashMap<Symbol, isize>,
-            associated_types: &mut FxHashMap<Symbol, isize>,
-            lastpathid: &mut isize,
-            crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
+            serialized_index: &mut SerializedSearchIndex,
+            used_in_function_signature: &mut BTreeSet<isize>,
             tcx: TyCtxt<'_>,
         ) {
             if let Some(generics) = &mut ty.generics {
@@ -271,11 +1596,8 @@ pub(crate) fn build_index(
                     convert_render_type(
                         item,
                         cache,
-                        itemid_to_pathid,
-                        primitives,
-                        associated_types,
-                        lastpathid,
-                        crate_paths,
+                        serialized_index,
+                        used_in_function_signature,
                         tcx,
                     );
                 }
@@ -285,11 +1607,8 @@ pub(crate) fn build_index(
                     let converted_associated_type = convert_render_type_id(
                         *associated_type,
                         cache,
-                        itemid_to_pathid,
-                        primitives,
-                        associated_types,
-                        lastpathid,
-                        crate_paths,
+                        serialized_index,
+                        used_in_function_signature,
                         tcx,
                     );
                     let Some(converted_associated_type) = converted_associated_type else {
@@ -300,11 +1619,8 @@ pub(crate) fn build_index(
                         convert_render_type(
                             constraint,
                             cache,
-                            itemid_to_pathid,
-                            primitives,
-                            associated_types,
-                            lastpathid,
-                            crate_paths,
+                            serialized_index,
+                            used_in_function_signature,
                             tcx,
                         );
                     }
@@ -318,24 +1634,74 @@ pub(crate) fn build_index(
             ty.id = convert_render_type_id(
                 id,
                 cache,
-                itemid_to_pathid,
-                primitives,
-                associated_types,
-                lastpathid,
-                crate_paths,
+                serialized_index,
+                used_in_function_signature,
                 tcx,
             );
+            use crate::clean::PrimitiveType;
+            // These cases are added to the inverted index, but not actually included
+            // in the signature. There's a matching set of cases in the
+            // `unifyFunctionTypeIsMatchCandidate` function, for the slow path.
+            match id {
+                // typeNameIdOfArrayOrSlice
+                RenderTypeId::Primitive(PrimitiveType::Array | PrimitiveType::Slice) => {
+                    insert_into_map(
+                        ItemType::Primitive,
+                        &[Symbol::intern("[]")],
+                        None,
+                        false,
+                        serialized_index,
+                        used_in_function_signature,
+                    );
+                }
+                RenderTypeId::Primitive(PrimitiveType::Tuple | PrimitiveType::Unit) => {
+                    // typeNameIdOfArrayOrSlice
+                    insert_into_map(
+                        ItemType::Primitive,
+                        &[Symbol::intern("()")],
+                        None,
+                        false,
+                        serialized_index,
+                        used_in_function_signature,
+                    );
+                }
+                // typeNameIdOfHof
+                RenderTypeId::Primitive(PrimitiveType::Fn) => {
+                    insert_into_map(
+                        ItemType::Primitive,
+                        &[Symbol::intern("->")],
+                        None,
+                        false,
+                        serialized_index,
+                        used_in_function_signature,
+                    );
+                }
+                RenderTypeId::DefId(did)
+                    if tcx.lang_items().fn_mut_trait() == Some(did)
+                        || tcx.lang_items().fn_once_trait() == Some(did)
+                        || tcx.lang_items().fn_trait() == Some(did) =>
+                {
+                    insert_into_map(
+                        ItemType::Primitive,
+                        &[Symbol::intern("->")],
+                        None,
+                        false,
+                        serialized_index,
+                        used_in_function_signature,
+                    );
+                }
+                // not special
+                _ => {}
+            }
         }
         if let Some(search_type) = &mut item.search_type {
+            let mut used_in_function_signature = BTreeSet::new();
             for item in &mut search_type.inputs {
                 convert_render_type(
                     item,
                     cache,
-                    &mut itemid_to_pathid,
-                    &mut primitives,
-                    &mut associated_types,
-                    &mut lastpathid,
-                    &mut crate_paths,
+                    &mut serialized_index,
+                    &mut used_in_function_signature,
                     tcx,
                 );
             }
@@ -343,11 +1709,8 @@ pub(crate) fn build_index(
                 convert_render_type(
                     item,
                     cache,
-                    &mut itemid_to_pathid,
-                    &mut primitives,
-                    &mut associated_types,
-                    &mut lastpathid,
-                    &mut crate_paths,
+                    &mut serialized_index,
+                    &mut used_in_function_signature,
                     tcx,
                 );
             }
@@ -356,464 +1719,56 @@ pub(crate) fn build_index(
                     convert_render_type(
                         trait_,
                         cache,
-                        &mut itemid_to_pathid,
-                        &mut primitives,
-                        &mut associated_types,
-                        &mut lastpathid,
-                        &mut crate_paths,
+                        &mut serialized_index,
+                        &mut used_in_function_signature,
                         tcx,
                     );
                 }
             }
-        }
-    }
-
-    let Cache { ref paths, ref exact_paths, ref external_paths, .. } = *cache;
-
-    // Then, on parent modules
-    let crate_items: Vec<&IndexItem> = search_index
-        .iter_mut()
-        .map(|item| {
-            item.parent_idx =
-                item.parent.and_then(|defid| match itemid_to_pathid.entry(ItemId::DefId(defid)) {
-                    Entry::Occupied(entry) => Some(*entry.get()),
-                    Entry::Vacant(entry) => {
-                        let pathid = lastpathid;
-                        entry.insert(pathid);
-                        lastpathid += 1;
-
-                        if let Some(&(ref fqp, short)) = paths.get(&defid) {
-                            let exact_fqp = exact_paths
-                                .get(&defid)
-                                .or_else(|| external_paths.get(&defid).map(|(fqp, _)| fqp))
-                                .filter(|exact_fqp| {
-                                    exact_fqp.last() == Some(&item.name) && *exact_fqp != fqp
-                                });
-                            crate_paths.push((
-                                short,
-                                fqp.clone(),
-                                exact_fqp.cloned(),
-                                utils::has_doc_flag(tcx, defid, sym::search_unbox),
-                            ));
-                            Some(pathid)
-                        } else {
-                            None
-                        }
-                    }
-                });
-
-            if let Some(defid) = item.defid
-                && item.parent_idx.is_none()
-            {
-                // If this is a re-export, retain the original path.
-                // Associated items don't use this.
-                // Their parent carries the exact fqp instead.
-                let exact_fqp = exact_paths
-                    .get(&defid)
-                    .or_else(|| external_paths.get(&defid).map(|(fqp, _)| fqp));
-                item.exact_path = exact_fqp.and_then(|fqp| {
-                    // Re-exports only count if the name is exactly the same.
-                    // This is a size optimization, since it means we only need
-                    // to store the name once (and the path is re-used for everything
-                    // exported from this same module). It's also likely to Do
-                    // What I Mean, since if a re-export changes the name, it might
-                    // also be a change in semantic meaning.
-                    if fqp.last() != Some(&item.name) {
-                        return None;
-                    }
-                    let path =
-                        if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) {
-                            // `#[macro_export]` always exports to the crate root.
-                            tcx.crate_name(defid.krate).to_string()
-                        } else {
-                            if fqp.len() < 2 {
-                                return None;
-                            }
-                            join_path_syms(&fqp[..fqp.len() - 1])
-                        };
-                    if path == item.path {
-                        return None;
-                    }
-                    Some(path)
-                });
-            } else if let Some(parent_idx) = item.parent_idx {
-                let i = <isize as TryInto<usize>>::try_into(parent_idx).unwrap();
-                item.path = {
-                    let p = &crate_paths[i].1;
-                    join_path_syms(&p[..p.len() - 1])
-                };
-                item.exact_path =
-                    crate_paths[i].2.as_ref().map(|xp| join_path_syms(&xp[..xp.len() - 1]));
-            }
-
-            // Omit the parent path if it is same to that of the prior item.
-            if lastpath == item.path {
-                item.path.clear();
-            } else {
-                lastpath = &item.path;
-            }
-
-            &*item
-        })
-        .collect();
-
-    // Find associated items that need disambiguators
-    let mut associated_item_duplicates = FxHashMap::<(isize, ItemType, Symbol), usize>::default();
-
-    for &item in &crate_items {
-        if item.impl_id.is_some()
-            && let Some(parent_idx) = item.parent_idx
-        {
-            let count =
-                associated_item_duplicates.entry((parent_idx, item.ty, item.name)).or_insert(0);
-            *count += 1;
-        }
-    }
-
-    let associated_item_disambiguators = crate_items
-        .iter()
-        .enumerate()
-        .filter_map(|(index, item)| {
-            let impl_id = ItemId::DefId(item.impl_id?);
-            let parent_idx = item.parent_idx?;
-            let count = *associated_item_duplicates.get(&(parent_idx, item.ty, item.name))?;
-            if count > 1 { Some((index, render::get_id_for_impl(tcx, impl_id))) } else { None }
-        })
-        .collect::<Vec<_>>();
-
-    struct CrateData<'a> {
-        items: Vec<&'a IndexItem>,
-        paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
-        // The String is alias name and the vec is the list of the elements with this alias.
-        //
-        // To be noted: the `usize` elements are indexes to `items`.
-        aliases: &'a AliasMap,
-        // Used when a type has more than one impl with an associated item with the same name.
-        associated_item_disambiguators: &'a Vec<(usize, String)>,
-        // A list of shard lengths encoded as vlqhex. See the comment in write_vlqhex_to_string
-        // for information on the format.
-        desc_index: String,
-        // A list of items with no description. This is eventually turned into a bitmap.
-        empty_desc: Vec<u32>,
-    }
-
-    struct Paths {
-        ty: ItemType,
-        name: Symbol,
-        path: Option<usize>,
-        exact_path: Option<usize>,
-        search_unbox: bool,
-    }
-
-    impl Serialize for Paths {
-        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-        where
-            S: Serializer,
-        {
-            let mut seq = serializer.serialize_seq(None)?;
-            seq.serialize_element(&self.ty)?;
-            seq.serialize_element(self.name.as_str())?;
-            if let Some(ref path) = self.path {
-                seq.serialize_element(path)?;
-            }
-            if let Some(ref path) = self.exact_path {
-                assert!(self.path.is_some());
-                seq.serialize_element(path)?;
-            }
-            if self.search_unbox {
-                if self.path.is_none() {
-                    seq.serialize_element(&None::<u8>)?;
-                }
-                if self.exact_path.is_none() {
-                    seq.serialize_element(&None::<u8>)?;
-                }
-                seq.serialize_element(&1)?;
-            }
-            seq.end()
-        }
-    }
-
-    impl Serialize for CrateData<'_> {
-        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-        where
-            S: Serializer,
-        {
-            let mut extra_paths = FxHashMap::default();
-            // We need to keep the order of insertion, hence why we use an `IndexMap`. Then we will
-            // insert these "extra paths" (which are paths of items from external crates) into the
-            // `full_paths` list at the end.
-            let mut revert_extra_paths = FxIndexMap::default();
-            let mut mod_paths = FxHashMap::default();
-            for (index, item) in self.items.iter().enumerate() {
-                if item.path.is_empty() {
-                    continue;
-                }
-                mod_paths.insert(&item.path, index);
-            }
-            let mut paths = Vec::with_capacity(self.paths.len());
-            for &(ty, ref path, ref exact, search_unbox) in &self.paths {
-                if path.len() < 2 {
-                    paths.push(Paths {
-                        ty,
-                        name: path[0],
-                        path: None,
-                        exact_path: None,
-                        search_unbox,
-                    });
-                    continue;
-                }
-                let full_path = join_path_syms(&path[..path.len() - 1]);
-                let full_exact_path = exact
-                    .as_ref()
-                    .filter(|exact| exact.last() == path.last() && exact.len() >= 2)
-                    .map(|exact| join_path_syms(&exact[..exact.len() - 1]));
-                let exact_path = extra_paths.len() + self.items.len();
-                let exact_path = full_exact_path.as_ref().map(|full_exact_path| match extra_paths
-                    .entry(full_exact_path.clone())
-                {
-                    Entry::Occupied(entry) => *entry.get(),
-                    Entry::Vacant(entry) => {
-                        if let Some(index) = mod_paths.get(&full_exact_path) {
-                            return *index;
-                        }
-                        entry.insert(exact_path);
-                        if !revert_extra_paths.contains_key(&exact_path) {
-                            revert_extra_paths.insert(exact_path, full_exact_path.clone());
-                        }
-                        exact_path
-                    }
-                });
-                if let Some(index) = mod_paths.get(&full_path) {
-                    paths.push(Paths {
-                        ty,
-                        name: *path.last().unwrap(),
-                        path: Some(*index),
-                        exact_path,
-                        search_unbox,
-                    });
-                    continue;
-                }
-                // It means it comes from an external crate so the item and its path will be
-                // stored into another array.
+            let search_type_size = search_type.size() +
+                // Artificially give struct fields a size of 8 instead of their real
+                // size of 2. This is because search.js sorts them to the end, so
+                // by pushing them down, we prevent them from blocking real 2-arity functions.
                 //
-                // `index` is put after the last `mod_paths`
-                let index = extra_paths.len() + self.items.len();
-                match extra_paths.entry(full_path.clone()) {
-                    Entry::Occupied(entry) => {
-                        paths.push(Paths {
-                            ty,
-                            name: *path.last().unwrap(),
-                            path: Some(*entry.get()),
-                            exact_path,
-                            search_unbox,
-                        });
-                    }
-                    Entry::Vacant(entry) => {
-                        entry.insert(index);
-                        if !revert_extra_paths.contains_key(&index) {
-                            revert_extra_paths.insert(index, full_path);
-                        }
-                        paths.push(Paths {
-                            ty,
-                            name: *path.last().unwrap(),
-                            path: Some(index),
-                            exact_path,
-                            search_unbox,
-                        });
-                    }
-                }
-            }
-
-            // Direct exports use adjacent arrays for the current crate's items,
-            // but re-exported exact paths don't.
-            let mut re_exports = Vec::new();
-            for (item_index, item) in self.items.iter().enumerate() {
-                if let Some(exact_path) = item.exact_path.as_ref() {
-                    if let Some(path_index) = mod_paths.get(&exact_path) {
-                        re_exports.push((item_index, *path_index));
-                    } else {
-                        let path_index = extra_paths.len() + self.items.len();
-                        let path_index = match extra_paths.entry(exact_path.clone()) {
-                            Entry::Occupied(entry) => *entry.get(),
-                            Entry::Vacant(entry) => {
-                                entry.insert(path_index);
-                                if !revert_extra_paths.contains_key(&path_index) {
-                                    revert_extra_paths.insert(path_index, exact_path.clone());
-                                }
-                                path_index
-                            }
-                        };
-                        re_exports.push((item_index, path_index));
-                    }
-                }
-            }
-
-            let mut names = Vec::with_capacity(self.items.len());
-            let mut types = String::with_capacity(self.items.len());
-            let mut full_paths = Vec::with_capacity(self.items.len());
-            let mut parents = String::with_capacity(self.items.len());
-            let mut parents_backref_queue = VecDeque::new();
-            let mut functions = String::with_capacity(self.items.len());
-            let mut deprecated = Vec::with_capacity(self.items.len());
-
-            let mut type_backref_queue = VecDeque::new();
-
-            let mut last_name = None;
-            for (index, item) in self.items.iter().enumerate() {
-                let n = item.ty as u8;
-                let c = char::from(n + b'A');
-                assert!(c <= 'z', "item types must fit within ASCII printables");
-                types.push(c);
-
-                assert_eq!(
-                    item.parent.is_some(),
-                    item.parent_idx.is_some(),
-                    "`{}` is missing idx",
-                    item.name
-                );
-                assert!(
-                    parents_backref_queue.len() <= 16,
-                    "the string encoding only supports 16 slots of lookback"
-                );
-                let parent: i32 = item.parent_idx.map(|x| x + 1).unwrap_or(0).try_into().unwrap();
-                if let Some(idx) = parents_backref_queue.iter().position(|p: &i32| *p == parent) {
-                    parents.push(
-                        char::try_from('0' as u32 + u32::try_from(idx).unwrap())
-                            .expect("last possible value is '?'"),
-                    );
-                } else if parent == 0 {
-                    write_vlqhex_to_string(parent, &mut parents);
-                } else {
-                    parents_backref_queue.push_front(parent);
-                    write_vlqhex_to_string(parent, &mut parents);
-                    if parents_backref_queue.len() > 16 {
-                        parents_backref_queue.pop_back();
-                    }
-                }
-
-                if Some(item.name.as_str()) == last_name {
-                    names.push("");
+                // The number 8 is arbitrary. We want it big, but not enormous,
+                // because the postings list has to fill in an empty array for each
+                // unoccupied size.
+                if item.ty.is_fn_like() { 0 } else { 16 };
+            serialized_index.function_data[new_entry_id] = Some(FunctionData {
+                function_signature: {
+                    let mut function_signature = String::new();
+                    search_type.write_to_string_without_param_names(&mut function_signature);
+                    function_signature
+                },
+                param_names: search_type
+                    .param_names
+                    .iter()
+                    .map(|sym| sym.map(|sym| sym.to_string()).unwrap_or(String::new()))
+                    .collect::<Vec<String>>(),
+            });
+            for index in used_in_function_signature {
+                let postings = if index >= 0 {
+                    assert!(serialized_index.path_data[index as usize].is_some());
+                    &mut serialized_index.type_data[index as usize]
+                        .as_mut()
+                        .unwrap()
+                        .inverted_function_signature_index
                 } else {
-                    names.push(item.name.as_str());
-                    last_name = Some(item.name.as_str());
-                }
-
-                if !item.path.is_empty() {
-                    full_paths.push((index, &item.path));
-                }
-
-                match &item.search_type {
-                    Some(ty) => ty.write_to_string(&mut functions, &mut type_backref_queue),
-                    None => functions.push('`'),
-                }
-
-                if item.deprecation.is_some() {
-                    // bitmasks always use 1-indexing for items, with 0 as the crate itself
-                    deprecated.push(u32::try_from(index + 1).unwrap());
-                }
-            }
-
-            for (index, path) in &revert_extra_paths {
-                full_paths.push((*index, path));
-            }
-
-            let param_names: Vec<(usize, String)> = {
-                let mut prev = Vec::new();
-                let mut result = Vec::new();
-                for (index, item) in self.items.iter().enumerate() {
-                    if let Some(ty) = &item.search_type
-                        && let my = ty
-                            .param_names
-                            .iter()
-                            .filter_map(|sym| sym.map(|sym| sym.to_string()))
-                            .collect::<Vec<_>>()
-                        && my != prev
-                    {
-                        result.push((index, my.join(",")));
-                        prev = my;
+                    let generic_id = usize::try_from(-index).unwrap() - 1;
+                    for _ in serialized_index.generic_inverted_index.len()..=generic_id {
+                        serialized_index.generic_inverted_index.push(Vec::new());
                     }
+                    &mut serialized_index.generic_inverted_index[generic_id]
+                };
+                while postings.len() <= search_type_size {
+                    postings.push(Vec::new());
                 }
-                result
-            };
-
-            let has_aliases = !self.aliases.is_empty();
-            let mut crate_data =
-                serializer.serialize_struct("CrateData", if has_aliases { 13 } else { 12 })?;
-            crate_data.serialize_field("t", &types)?;
-            crate_data.serialize_field("n", &names)?;
-            crate_data.serialize_field("q", &full_paths)?;
-            crate_data.serialize_field("i", &parents)?;
-            crate_data.serialize_field("f", &functions)?;
-            crate_data.serialize_field("D", &self.desc_index)?;
-            crate_data.serialize_field("p", &paths)?;
-            crate_data.serialize_field("r", &re_exports)?;
-            crate_data.serialize_field("b", &self.associated_item_disambiguators)?;
-            crate_data.serialize_field("c", &bitmap_to_string(&deprecated))?;
-            crate_data.serialize_field("e", &bitmap_to_string(&self.empty_desc))?;
-            crate_data.serialize_field("P", &param_names)?;
-            if has_aliases {
-                crate_data.serialize_field("a", &self.aliases)?;
+                postings[search_type_size].push(new_entry_id as u32);
             }
-            crate_data.end()
         }
     }
 
-    let (empty_desc, desc) = {
-        let mut empty_desc = Vec::new();
-        let mut result = Vec::new();
-        let mut set = String::new();
-        let mut len: usize = 0;
-        let mut item_index: u32 = 0;
-        for desc in std::iter::once(&crate_doc).chain(crate_items.iter().map(|item| &item.desc)) {
-            if desc.is_empty() {
-                empty_desc.push(item_index);
-                item_index += 1;
-                continue;
-            }
-            if set.len() >= DESC_INDEX_SHARD_LEN {
-                result.push((len, std::mem::take(&mut set)));
-                len = 0;
-            } else if len != 0 {
-                set.push('\n');
-            }
-            set.push_str(desc);
-            len += 1;
-            item_index += 1;
-        }
-        result.push((len, std::mem::take(&mut set)));
-        (empty_desc, result)
-    };
-
-    let desc_index = {
-        let mut desc_index = String::with_capacity(desc.len() * 4);
-        for &(len, _) in desc.iter() {
-            write_vlqhex_to_string(len.try_into().unwrap(), &mut desc_index);
-        }
-        desc_index
-    };
-
-    assert_eq!(
-        crate_items.len() + 1,
-        desc.iter().map(|(len, _)| *len).sum::<usize>() + empty_desc.len()
-    );
-
-    // The index, which is actually used to search, is JSON
-    // It uses `JSON.parse(..)` to actually load, since JSON
-    // parses faster than the full JavaScript syntax.
-    let crate_name = krate.name(tcx);
-    let data = CrateData {
-        items: crate_items,
-        paths: crate_paths,
-        aliases: &aliases,
-        associated_item_disambiguators: &associated_item_disambiguators,
-        desc_index,
-        empty_desc,
-    };
-    let index = OrderedJson::array_unsorted([
-        OrderedJson::serialize(crate_name.as_str()).unwrap(),
-        OrderedJson::serialize(data).unwrap(),
-    ]);
-    SerializedSearchIndex { index, desc }
+    Ok(serialized_index.sort())
 }
 
 pub(crate) fn get_function_type_for_search(
@@ -902,7 +1857,7 @@ fn get_index_type_id(
         }
         clean::Primitive(p) => Some(RenderTypeId::Primitive(p)),
         clean::BorrowedRef { .. } => Some(RenderTypeId::Primitive(clean::PrimitiveType::Reference)),
-        clean::RawPointer(_, ref type_) => get_index_type_id(type_, rgen),
+        clean::RawPointer { .. } => Some(RenderTypeId::Primitive(clean::PrimitiveType::RawPointer)),
         // The type parameters are converted to generics in `simplify_fn_type`
         clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
         clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
@@ -1160,7 +2115,8 @@ fn simplify_fn_type<'a, 'tcx>(
                 generics: Some(ty_generics),
             });
         }
-        Type::BorrowedRef { lifetime: _, mutability, ref type_ } => {
+        Type::BorrowedRef { lifetime: _, mutability, ref type_ }
+        | Type::RawPointer(mutability, ref type_) => {
             let mut ty_generics = Vec::new();
             if mutability.is_mut() {
                 ty_generics.push(RenderType {
diff --git a/src/librustdoc/html/render/search_index/encode.rs b/src/librustdoc/html/render/search_index/encode.rs
index de2f54558ff..d15e13a2d37 100644
--- a/src/librustdoc/html/render/search_index/encode.rs
+++ b/src/librustdoc/html/render/search_index/encode.rs
@@ -1,6 +1,4 @@
-use base64::prelude::*;
-
-pub(crate) fn write_vlqhex_to_string(n: i32, string: &mut String) {
+pub(crate) fn write_signed_vlqhex_to_string(n: i32, string: &mut String) {
     let (sign, magnitude): (bool, u32) =
         if n >= 0 { (false, n.try_into().unwrap()) } else { (true, (-n).try_into().unwrap()) };
     // zig-zag encoding
@@ -37,206 +35,66 @@ pub(crate) fn write_vlqhex_to_string(n: i32, string: &mut String) {
     }
 }
 
-// Used during bitmap encoding
-enum Container {
-    /// number of ones, bits
-    Bits(Box<[u64; 1024]>),
-    /// list of entries
-    Array(Vec<u16>),
-    /// list of (start, len-1)
-    Run(Vec<(u16, u16)>),
-}
-impl Container {
-    fn popcount(&self) -> u32 {
-        match self {
-            Container::Bits(bits) => bits.iter().copied().map(|x| x.count_ones()).sum(),
-            Container::Array(array) => {
-                array.len().try_into().expect("array can't be bigger than 2**32")
-            }
-            Container::Run(runs) => {
-                runs.iter().copied().map(|(_, lenm1)| u32::from(lenm1) + 1).sum()
-            }
+pub fn read_signed_vlqhex_from_string(string: &[u8]) -> Option<(i32, usize)> {
+    let mut n = 0i32;
+    let mut i = 0;
+    while let Some(&c) = string.get(i) {
+        i += 1;
+        n = (n << 4) | i32::from(c & 0xF);
+        if c >= 96 {
+            // zig-zag encoding
+            let (sign, magnitude) = (n & 1, n >> 1);
+            let value = if sign == 0 { 1 } else { -1 } * magnitude;
+            return Some((value, i));
         }
     }
-    fn push(&mut self, value: u16) {
-        match self {
-            Container::Bits(bits) => bits[value as usize >> 6] |= 1 << (value & 0x3F),
-            Container::Array(array) => {
-                array.push(value);
-                if array.len() >= 4096 {
-                    let array = std::mem::take(array);
-                    *self = Container::Bits(Box::new([0; 1024]));
-                    for value in array {
-                        self.push(value);
-                    }
-                }
-            }
-            Container::Run(runs) => {
-                if let Some(r) = runs.last_mut()
-                    && r.0 + r.1 + 1 == value
-                {
-                    r.1 += 1;
-                } else {
-                    runs.push((value, 0));
-                }
-            }
+    None
+}
+
+pub fn write_postings_to_string(postings: &[Vec<u32>], buf: &mut Vec<u8>) {
+    for list in postings {
+        if list.is_empty() {
+            buf.push(0);
+            continue;
         }
-    }
-    fn try_make_run(&mut self) -> bool {
-        match self {
-            Container::Bits(bits) => {
-                let mut r: u64 = 0;
-                for (i, chunk) in bits.iter().copied().enumerate() {
-                    let next_chunk =
-                        i.checked_add(1).and_then(|i| bits.get(i)).copied().unwrap_or(0);
-                    r += !chunk & u64::from((chunk << 1).count_ones());
-                    r += !next_chunk & u64::from((chunk >> 63).count_ones());
-                }
-                if (2 + 4 * r) >= 8192 {
-                    return false;
-                }
-                let bits = std::mem::replace(bits, Box::new([0; 1024]));
-                *self = Container::Run(Vec::new());
-                for (i, bits) in bits.iter().copied().enumerate() {
-                    if bits == 0 {
-                        continue;
-                    }
-                    for j in 0..64 {
-                        let value = (u16::try_from(i).unwrap() << 6) | j;
-                        if bits & (1 << j) != 0 {
-                            self.push(value);
-                        }
-                    }
-                }
-                true
+        let len_before = buf.len();
+        stringdex::internals::encode::write_bitmap_to_bytes(&list, &mut *buf).unwrap();
+        let len_after = buf.len();
+        if len_after - len_before > 1 + (4 * list.len()) && list.len() < 0x3a {
+            buf.truncate(len_before);
+            buf.push(list.len() as u8);
+            for &item in list {
+                buf.push(item as u8);
+                buf.push((item >> 8) as u8);
+                buf.push((item >> 16) as u8);
+                buf.push((item >> 24) as u8);
             }
-            Container::Array(array) if array.len() <= 5 => false,
-            Container::Array(array) => {
-                let mut r = 0;
-                let mut prev = None;
-                for value in array.iter().copied() {
-                    if value.checked_sub(1) != prev {
-                        r += 1;
-                    }
-                    prev = Some(value);
-                }
-                if 2 + 4 * r >= 2 * array.len() + 2 {
-                    return false;
-                }
-                let array = std::mem::take(array);
-                *self = Container::Run(Vec::new());
-                for value in array {
-                    self.push(value);
-                }
-                true
-            }
-            Container::Run(_) => true,
         }
     }
 }
 
-// checked against roaring-rs in
-// https://gitlab.com/notriddle/roaring-test
-pub(crate) fn write_bitmap_to_bytes(
-    domain: &[u32],
-    mut out: impl std::io::Write,
-) -> std::io::Result<()> {
-    // https://arxiv.org/pdf/1603.06549.pdf
-    let mut keys = Vec::<u16>::new();
-    let mut containers = Vec::<Container>::new();
-    let mut key: u16;
-    let mut domain_iter = domain.iter().copied().peekable();
-    let mut has_run = false;
-    while let Some(entry) = domain_iter.next() {
-        key = (entry >> 16).try_into().expect("shifted off the top 16 bits, so it should fit");
-        let value: u16 = (entry & 0x00_00_FF_FF).try_into().expect("AND 16 bits, so it should fit");
-        let mut container = Container::Array(vec![value]);
-        while let Some(entry) = domain_iter.peek().copied() {
-            let entry_key: u16 =
-                (entry >> 16).try_into().expect("shifted off the top 16 bits, so it should fit");
-            if entry_key != key {
-                break;
+pub fn read_postings_from_string(postings: &mut Vec<Vec<u32>>, mut buf: &[u8]) {
+    use stringdex::internals::decode::RoaringBitmap;
+    while let Some(&c) = buf.get(0) {
+        if c < 0x3a {
+            buf = &buf[1..];
+            let mut slot = Vec::new();
+            for _ in 0..c {
+                slot.push(
+                    (buf[0] as u32)
+                        | ((buf[1] as u32) << 8)
+                        | ((buf[2] as u32) << 16)
+                        | ((buf[3] as u32) << 24),
+                );
+                buf = &buf[4..];
             }
-            domain_iter.next().expect("peeking just succeeded");
-            container
-                .push((entry & 0x00_00_FF_FF).try_into().expect("AND 16 bits, so it should fit"));
-        }
-        keys.push(key);
-        has_run = container.try_make_run() || has_run;
-        containers.push(container);
-    }
-    // https://github.com/RoaringBitmap/RoaringFormatSpec
-    const SERIAL_COOKIE_NO_RUNCONTAINER: u32 = 12346;
-    const SERIAL_COOKIE: u32 = 12347;
-    const NO_OFFSET_THRESHOLD: u32 = 4;
-    let size: u32 = containers.len().try_into().unwrap();
-    let start_offset = if has_run {
-        out.write_all(&u32::to_le_bytes(SERIAL_COOKIE | ((size - 1) << 16)))?;
-        for set in containers.chunks(8) {
-            let mut b = 0;
-            for (i, container) in set.iter().enumerate() {
-                if matches!(container, &Container::Run(..)) {
-                    b |= 1 << i;
-                }
-            }
-            out.write_all(&[b])?;
-        }
-        if size < NO_OFFSET_THRESHOLD {
-            4 + 4 * size + size.div_ceil(8)
+            postings.push(slot);
         } else {
-            4 + 8 * size + size.div_ceil(8)
-        }
-    } else {
-        out.write_all(&u32::to_le_bytes(SERIAL_COOKIE_NO_RUNCONTAINER))?;
-        out.write_all(&u32::to_le_bytes(containers.len().try_into().unwrap()))?;
-        4 + 4 + 4 * size + 4 * size
-    };
-    for (&key, container) in keys.iter().zip(&containers) {
-        // descriptive header
-        let key: u32 = key.into();
-        let count: u32 = container.popcount() - 1;
-        out.write_all(&u32::to_le_bytes((count << 16) | key))?;
-    }
-    if !has_run || size >= NO_OFFSET_THRESHOLD {
-        // offset header
-        let mut starting_offset = start_offset;
-        for container in &containers {
-            out.write_all(&u32::to_le_bytes(starting_offset))?;
-            starting_offset += match container {
-                Container::Bits(_) => 8192u32,
-                Container::Array(array) => u32::try_from(array.len()).unwrap() * 2,
-                Container::Run(runs) => 2 + u32::try_from(runs.len()).unwrap() * 4,
-            };
+            let (bitmap, consumed_bytes_len) =
+                RoaringBitmap::from_bytes(buf).unwrap_or_else(|| (RoaringBitmap::default(), 0));
+            assert_ne!(consumed_bytes_len, 0);
+            postings.push(bitmap.to_vec());
+            buf = &buf[consumed_bytes_len..];
         }
     }
-    for container in &containers {
-        match container {
-            Container::Bits(bits) => {
-                for chunk in bits.iter() {
-                    out.write_all(&u64::to_le_bytes(*chunk))?;
-                }
-            }
-            Container::Array(array) => {
-                for value in array.iter() {
-                    out.write_all(&u16::to_le_bytes(*value))?;
-                }
-            }
-            Container::Run(runs) => {
-                out.write_all(&u16::to_le_bytes(runs.len().try_into().unwrap()))?;
-                for (start, lenm1) in runs.iter().copied() {
-                    out.write_all(&u16::to_le_bytes(start))?;
-                    out.write_all(&u16::to_le_bytes(lenm1))?;
-                }
-            }
-        }
-    }
-    Ok(())
-}
-
-pub(crate) fn bitmap_to_string(domain: &[u32]) -> String {
-    let mut buf = Vec::new();
-    let mut strbuf = String::new();
-    write_bitmap_to_bytes(domain, &mut buf).unwrap();
-    BASE64_STANDARD.encode_string(&buf, &mut strbuf);
-    strbuf
 }
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
index 846d3ad310c..8bc2e0bd957 100644
--- a/src/librustdoc/html/render/span_map.rs
+++ b/src/librustdoc/html/render/span_map.rs
@@ -35,7 +35,7 @@ pub(crate) enum LinkFromSrc {
 /// 1. Generate a `span` correspondence map which links an item `span` to its definition `span`.
 /// 2. Collect the source code files.
 ///
-/// It returns the `krate`, the source code files and the `span` correspondence map.
+/// It returns the source code files and the `span` correspondence map.
 ///
 /// Note about the `span` correspondence map: the keys are actually `(lo, hi)` of `span`s. We don't
 /// need the `span` context later on, only their position, so instead of keeping a whole `Span`, we
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index 1f691392b17..e37a5246a76 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -65,17 +65,17 @@ pub(crate) fn write_shared(
     // Write shared runs within a flock; disable thread dispatching of IO temporarily.
     let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
 
-    let SerializedSearchIndex { index, desc } = build_index(krate, &mut cx.shared.cache, tcx);
-    write_search_desc(cx, krate, &desc)?; // does not need to be merged
+    let search_index =
+        build_index(krate, &mut cx.shared.cache, tcx, &cx.dst, &cx.shared.resource_suffix)?;
 
     let crate_name = krate.name(cx.tcx());
     let crate_name = crate_name.as_str(); // rand
     let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
     let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
     let info = CrateInfo {
-        version: CrateInfoVersion::V1,
+        version: CrateInfoVersion::V2,
         src_files_js: SourcesPart::get(cx, &crate_name_json)?,
-        search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
+        search_index,
         all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
         crates_index: CratesIndexPart::get(crate_name, &external_crates)?,
         trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
@@ -141,7 +141,7 @@ pub(crate) fn write_not_crate_specific(
     resource_suffix: &str,
     include_sources: bool,
 ) -> Result<(), Error> {
-    write_rendered_cross_crate_info(crates, dst, opt, include_sources)?;
+    write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
     write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
     Ok(())
 }
@@ -151,13 +151,18 @@ fn write_rendered_cross_crate_info(
     dst: &Path,
     opt: &RenderOptions,
     include_sources: bool,
+    resource_suffix: &str,
 ) -> Result<(), Error> {
     let m = &opt.should_merge;
     if opt.should_emit_crate() {
         if include_sources {
             write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
         }
-        write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, crates, m)?;
+        crates
+            .iter()
+            .fold(SerializedSearchIndex::default(), |a, b| a.union(&b.search_index))
+            .sort()
+            .write_to(dst, resource_suffix)?;
         write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
     }
     write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
@@ -215,38 +220,12 @@ fn write_static_files(
     Ok(())
 }
 
-/// Write the search description shards to disk
-fn write_search_desc(
-    cx: &mut Context<'_>,
-    krate: &Crate,
-    search_desc: &[(usize, String)],
-) -> Result<(), Error> {
-    let crate_name = krate.name(cx.tcx()).to_string();
-    let encoded_crate_name = OrderedJson::serialize(&crate_name).unwrap();
-    let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]);
-    if path.exists() {
-        try_err!(fs::remove_dir_all(&path), &path);
-    }
-    for (i, (_, part)) in search_desc.iter().enumerate() {
-        let filename = static_files::suffix_path(
-            &format!("{crate_name}-desc-{i}-.js"),
-            &cx.shared.resource_suffix,
-        );
-        let path = path.join(filename);
-        let part = OrderedJson::serialize(part).unwrap();
-        let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})");
-        create_parents(&path)?;
-        try_err!(fs::write(&path, part), &path);
-    }
-    Ok(())
-}
-
 /// Contains pre-rendered contents to insert into the CCI template
 #[derive(Serialize, Deserialize, Clone, Debug)]
 pub(crate) struct CrateInfo {
     version: CrateInfoVersion,
     src_files_js: PartsAndLocations<SourcesPart>,
-    search_index_js: PartsAndLocations<SearchIndexPart>,
+    search_index: SerializedSearchIndex,
     all_crates: PartsAndLocations<AllCratesPart>,
     crates_index: PartsAndLocations<CratesIndexPart>,
     trait_impl: PartsAndLocations<TraitAliasPart>,
@@ -277,7 +256,7 @@ impl CrateInfo {
 /// to provide better diagnostics about including an invalid file.
 #[derive(Serialize, Deserialize, Clone, Debug)]
 enum CrateInfoVersion {
-    V1,
+    V2,
 }
 
 /// Paths (relative to the doc root) and their pre-merge contents
@@ -332,36 +311,6 @@ trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
 }
 
 #[derive(Serialize, Deserialize, Clone, Default, Debug)]
-struct SearchIndex;
-type SearchIndexPart = Part<SearchIndex, EscapedJson>;
-impl CciPart for SearchIndexPart {
-    type FileFormat = sorted_template::Js;
-    fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
-        &crate_info.search_index_js
-    }
-}
-
-impl SearchIndexPart {
-    fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
-        SortedTemplate::from_before_after(
-            r"var searchIndex = new Map(JSON.parse('[",
-            r"]'));
-if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
-else if (window.initSearch) window.initSearch(searchIndex);",
-        )
-    }
-
-    fn get(
-        search_index: OrderedJson,
-        resource_suffix: &str,
-    ) -> Result<PartsAndLocations<Self>, Error> {
-        let path = suffix_path("search-index.js", resource_suffix);
-        let search_index = EscapedJson::from(search_index);
-        Ok(PartsAndLocations::with(path, search_index))
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone, Default, Debug)]
 struct AllCrates;
 type AllCratesPart = Part<AllCrates, OrderedJson>;
 impl CciPart for AllCratesPart {
@@ -426,6 +375,7 @@ impl CratesIndexPart {
     fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
         let page = layout::Page {
             title: "Index of crates",
+            short_title: "Crates",
             css_class: "mod sys",
             root_path: "./",
             static_root_path: cx.shared.static_root_path.as_deref(),
diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs
index 6f185e85345..1989a1f87aa 100644
--- a/src/librustdoc/html/render/write_shared/tests.rs
+++ b/src/librustdoc/html/render/write_shared/tests.rs
@@ -30,14 +30,6 @@ fn sources_template() {
 }
 
 #[test]
-fn sources_parts() {
-    let parts =
-        SearchIndexPart::get(OrderedJson::serialize(["foo", "bar"]).unwrap(), "suffix").unwrap();
-    assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js"));
-    assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#);
-}
-
-#[test]
 fn all_crates_template() {
     let mut template = AllCratesPart::blank();
     assert_eq!(but_last_line(&template.to_string()), r"window.ALL_CRATES = [];");
@@ -55,31 +47,6 @@ fn all_crates_parts() {
 }
 
 #[test]
-fn search_index_template() {
-    let mut template = SearchIndexPart::blank();
-    assert_eq!(
-        but_last_line(&template.to_string()),
-        r"var searchIndex = new Map(JSON.parse('[]'));
-if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
-else if (window.initSearch) window.initSearch(searchIndex);"
-    );
-    template.append(EscapedJson::from(OrderedJson::serialize([1, 2]).unwrap()).to_string());
-    assert_eq!(
-        but_last_line(&template.to_string()),
-        r"var searchIndex = new Map(JSON.parse('[[1,2]]'));
-if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
-else if (window.initSearch) window.initSearch(searchIndex);"
-    );
-    template.append(EscapedJson::from(OrderedJson::serialize([4, 3]).unwrap()).to_string());
-    assert_eq!(
-        but_last_line(&template.to_string()),
-        r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]'));
-if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
-else if (window.initSearch) window.initSearch(searchIndex);"
-    );
-}
-
-#[test]
 fn crates_index_part() {
     let external_crates = ["bar".to_string(), "baz".to_string()];
     let mut parts = CratesIndexPart::get("foo", &external_crates).unwrap();
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index c34b3154269..9c5518a780e 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -230,6 +230,7 @@ impl SourceCollector<'_, '_> {
         );
         let page = layout::Page {
             title: &title,
+            short_title: &src_fname.to_string_lossy(),
             css_class: "src",
             root_path: &root_path,
             static_root_path: shared.static_root_path.as_deref(),
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index c48863b4681..86f1a42bc01 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -258,6 +258,17 @@ h1, h2, h3, h4 {
 	padding-bottom: 6px;
 	margin-bottom: 15px;
 }
+.search-results-main-heading {
+	grid-template-areas:
+		"main-heading-breadcrumbs main-heading-placeholder"
+		"main-heading-breadcrumbs main-heading-toolbar    "
+		"main-heading-h1          main-heading-toolbar    ";
+}
+.search-results-main-heading nav.sub {
+	grid-area: main-heading-h1;
+	align-items: end;
+	margin: 4px 0 8px 0;
+}
 .rustdoc-breadcrumbs {
 	grid-area: main-heading-breadcrumbs;
 	line-height: 1.25;
@@ -265,6 +276,16 @@ h1, h2, h3, h4 {
 	position: relative;
 	z-index: 1;
 }
+.search-switcher {
+	grid-area: main-heading-breadcrumbs;
+	line-height: 1.5;
+	display: flex;
+	color: var(--main-color);
+	align-items: baseline;
+	white-space: nowrap;
+	padding-top: 8px;
+	min-height: 34px;
+}
 .rustdoc-breadcrumbs a {
 	padding: 5px 0 7px;
 }
@@ -305,7 +326,7 @@ h4.code-header {
 #crate-search,
 h1, h2, h3, h4, h5, h6,
 .sidebar,
-.mobile-topbar,
+rustdoc-topbar,
 .search-input,
 .search-results .result-name,
 .item-table dt > a,
@@ -317,6 +338,7 @@ rustdoc-toolbar,
 summary.hideme,
 .scraped-example-list,
 .rustdoc-breadcrumbs,
+.search-switcher,
 /* This selector is for the items listed in the "all items" page. */
 ul.all-items {
 	font-family: "Fira Sans", Arial, NanumBarunGothic, sans-serif;
@@ -329,7 +351,7 @@ a.anchor,
 .rust a,
 .sidebar h2 a,
 .sidebar h3 a,
-.mobile-topbar h2 a,
+rustdoc-topbar h2 a,
 h1 a,
 .search-results a,
 .search-results li,
@@ -616,7 +638,7 @@ img {
 	color: var(--sidebar-resizer-active);
 }
 
-.sidebar, .mobile-topbar, .sidebar-menu-toggle,
+.sidebar, rustdoc-topbar, .sidebar-menu-toggle,
 #src-sidebar {
 	background-color: var(--sidebar-background-color);
 }
@@ -857,7 +879,7 @@ ul.block, .block li, .block ul {
 	margin-bottom: 1rem;
 }
 
-.mobile-topbar {
+rustdoc-topbar {
 	display: none;
 }
 
@@ -934,6 +956,40 @@ ul.block, .block li, .block ul {
 .example-wrap.digits-8 { --example-wrap-digits-count: 8ch; }
 .example-wrap.digits-9 { --example-wrap-digits-count: 9ch; }
 
+.example-wrap .expansion {
+	position: relative;
+	display: inline;
+}
+.example-wrap .expansion > input {
+	display: block;
+	position: absolute;
+	appearance: none;
+	content: '↕';
+	left: -20px;
+	top: 0;
+	border: 1px solid var(--border-color);
+	border-radius: 4px;
+	cursor: pointer;
+	color: var(--main-color);
+	padding: 0 2px;
+	line-height: 20px;
+}
+.example-wrap .expansion > input::after {
+	content: "↕";
+}
+.example-wrap .expansion .expanded {
+	display: none;
+	color: var(--main-color);
+}
+.example-wrap .expansion > input:checked ~ .expanded,
+.example-wrap .expansion > input:checked ~ * .expanded {
+	display: inherit;
+}
+.example-wrap .expansion > input:checked ~ .original,
+.example-wrap .expansion > input:checked ~ * .original {
+	display: none;
+}
+
 .example-wrap [data-nosnippet] {
 	width: calc(var(--example-wrap-digits-count) + var(--line-number-padding) * 2);
 }
@@ -942,6 +998,17 @@ ul.block, .block li, .block ul {
 		var(--example-wrap-digits-count) + var(--line-number-padding) * 2
 		+ var(--line-number-right-margin));
 }
+.src .example-wrap .expansion [data-nosnippet] {
+	/* FIXME: Once <https://bugzilla.mozilla.org/show_bug.cgi?id=1949948> is solved, uncomment
+	   next line and remove the two other rules. */
+	/*left: calc((
+		var(--example-wrap-digits-count) + var(--line-number-padding) * 2
+		+ var(--line-number-right-margin)) * -1);*/
+	position: initial;
+	margin-left: calc((
+		var(--example-wrap-digits-count) + var(--line-number-padding) * 2
+		+ var(--line-number-right-margin)) * -1);
+}
 
 .example-wrap [data-nosnippet] {
 	color: var(--src-line-numbers-span-color);
@@ -956,9 +1023,6 @@ ul.block, .block li, .block ul {
 	position: absolute;
 	left: 0;
 }
-.example-wrap .line-highlighted[data-nosnippet] {
-	background-color: var(--src-line-number-highlighted-background-color);
-}
 .example-wrap pre > code {
 	position: relative;
 	display: block;
@@ -973,6 +1037,9 @@ ul.block, .block li, .block ul {
 .example-wrap [data-nosnippet]:target {
 	border-right: none;
 }
+.example-wrap .line-highlighted[data-nosnippet] {
+	background-color: var(--src-line-number-highlighted-background-color);
+}
 .example-wrap.hide-lines [data-nosnippet] {
 	display: none;
 }
@@ -1098,16 +1165,15 @@ div.where {
 nav.sub {
 	flex-grow: 1;
 	flex-flow: row nowrap;
-	margin: 4px 0 0 0;
 	display: flex;
-	align-items: center;
+	align-items: start;
+	margin-top: 4px;
 }
 .search-form {
 	position: relative;
 	display: flex;
 	height: 34px;
 	flex-grow: 1;
-	margin-bottom: 4px;
 }
 .src nav.sub {
 	margin: 0 0 -10px 0;
@@ -1208,27 +1274,14 @@ table,
 	margin-left: 0;
 }
 
-.search-results-title {
-	margin-top: 0;
-	white-space: nowrap;
-	/* flex layout allows shrinking the <select> appropriately if it becomes too large */
-	display: flex;
-	/* make things look like in a line, despite the fact that we're using a layout
-	with boxes (i.e. from the flex layout) */
-	align-items: baseline;
-}
-.search-results-title + .sub-heading {
-	color: var(--main-color);
-	display: flex;
-	align-items: baseline;
-	white-space: nowrap;
-}
 #crate-search-div {
 	/* ensures that 100% in properties of #crate-search-div:after
 	are relative to the size of this div */
 	position: relative;
 	/* allows this div (and with it the <select>-element "#crate-search") to be shrunk */
 	min-width: 0;
+	/* keep label text for switcher from moving down when this appears */
+	margin-top: -1px;
 }
 #crate-search {
 	padding: 0 23px 0 4px;
@@ -1294,6 +1347,7 @@ so that we can apply CSS-filters to change the arrow color in themes */
 	flex-grow: 1;
 	background-color: var(--button-background-color);
 	color: var(--search-color);
+	max-width: 100%;
 }
 .search-input:focus {
 	border-color: var(--search-input-focused-border-color);
@@ -1459,14 +1513,14 @@ so that we can apply CSS-filters to change the arrow color in themes */
 }
 
 #settings.popover {
-	--popover-arrow-offset: 202px;
+	--popover-arrow-offset: 196px;
 	top: calc(100% - 16px);
 }
 
 /* use larger max-width for help popover, but not for help.html */
 #help.popover {
 	max-width: 600px;
-	--popover-arrow-offset: 118px;
+	--popover-arrow-offset: 115px;
 	top: calc(100% - 16px);
 }
 
@@ -1929,10 +1983,12 @@ a.tooltip:hover::after {
 	color: inherit;
 }
 #search-tabs button:not(.selected) {
+	--search-tab-button-background: var(--search-tab-button-not-selected-background);
 	background-color: var(--search-tab-button-not-selected-background);
 	border-top-color: var(--search-tab-button-not-selected-border-top-color);
 }
 #search-tabs button:hover, #search-tabs button.selected {
+	--search-tab-button-background: var(--search-tab-button-selected-background);
 	background-color: var(--search-tab-button-selected-background);
 	border-top-color: var(--search-tab-button-selected-border-top-color);
 }
@@ -1941,6 +1997,73 @@ a.tooltip:hover::after {
 	font-size: 1rem;
 	font-variant-numeric: tabular-nums;
 	color: var(--search-tab-title-count-color);
+	position: relative;
+}
+
+#search-tabs .count.loading {
+	color: transparent;
+}
+
+.search-form.loading {
+	--search-tab-button-background: var(--button-background-color);
+}
+
+#search-tabs .count.loading::before,
+.search-form.loading::before
+{
+	width: 16px;
+	height: 16px;
+	border-radius: 16px;
+	background: radial-gradient(
+		var(--search-tab-button-background) 0 50%,
+		transparent 50% 100%
+	), conic-gradient(
+		var(--code-highlight-kw-color) 0deg 30deg,
+		var(--code-highlight-prelude-color) 30deg 60deg,
+		var(--code-highlight-number-color) 90deg 120deg,
+		var(--code-highlight-lifetime-color ) 120deg 150deg,
+		var(--code-highlight-comment-color) 150deg 180deg,
+		var(--code-highlight-self-color) 180deg 210deg,
+		var(--code-highlight-attribute-color) 210deg 240deg,
+		var(--code-highlight-literal-color) 210deg 240deg,
+		var(--code-highlight-macro-color) 240deg 270deg,
+		var(--code-highlight-question-mark-color) 270deg 300deg,
+		var(--code-highlight-prelude-val-color) 300deg 330deg,
+		var(--code-highlight-doc-comment-color) 330deg 360deg
+	);
+	content: "";
+	position: absolute;
+	left: 2px;
+	top: 2px;
+	animation: rotating 1.25s linear infinite;
+}
+#search-tabs .count.loading::after,
+.search-form.loading::after
+{
+	width: 18px;
+	height: 18px;
+	border-radius: 18px;
+	background: conic-gradient(
+		var(--search-tab-button-background) 0deg 180deg,
+		transparent 270deg 360deg
+	);
+	content: "";
+	position: absolute;
+	left: 1px;
+	top: 1px;
+	animation: rotating 0.66s linear infinite;
+}
+
+.search-form.loading::before {
+	left: auto;
+	right: 9px;
+	top: 8px;
+}
+
+.search-form.loading::after {
+	left: auto;
+	right: 8px;
+	top: 8px;
 }
 
 #search .error code {
@@ -1974,7 +2097,7 @@ a.tooltip:hover::after {
 	border-bottom: 1px solid var(--border-color);
 }
 
-#settings-menu, #help-button, button#toggle-all-docs {
+#search-button, .settings-menu, .help-menu, button#toggle-all-docs {
 	margin-left: var(--button-left-margin);
 	display: flex;
 	line-height: 1.25;
@@ -1989,69 +2112,100 @@ a.tooltip:hover::after {
 	display: flex;
 	margin-right: 4px;
 	position: fixed;
+	margin-top: 25px;
+	left: 6px;
 	height: 34px;
 	width: 34px;
+	z-index: calc(var(--desktop-sidebar-z-index) + 1);
 }
 .hide-sidebar #sidebar-button {
 	left: 6px;
 	background-color: var(--main-background-color);
-	z-index: 1;
 }
 .src #sidebar-button {
+	margin-top: 0;
+	top: 8px;
 	left: 8px;
-	z-index: calc(var(--desktop-sidebar-z-index) + 1);
+	border-color: var(--border-color);
 }
 .hide-sidebar .src #sidebar-button {
 	position: static;
 }
-#settings-menu > a, #help-button > a, #sidebar-button > a, button#toggle-all-docs {
+#search-button > a,
+.settings-menu > a,
+.help-menu > a,
+#sidebar-button > a,
+button#toggle-all-docs {
 	display: flex;
 	align-items: center;
 	justify-content: center;
 	flex-direction: column;
 }
-#settings-menu > a, #help-button > a, button#toggle-all-docs {
+#search-button > a,
+.settings-menu > a,
+.help-menu > a,
+button#toggle-all-docs {
 	border: 1px solid transparent;
 	border-radius: var(--button-border-radius);
 	color: var(--main-color);
 }
-#settings-menu > a, #help-button > a, button#toggle-all-docs {
+#search-button > a, .settings-menu > a, .help-menu > a, button#toggle-all-docs {
 	width: 80px;
 	border-radius: var(--toolbar-button-border-radius);
 }
-#settings-menu > a, #help-button > a {
+#search-button > a, .settings-menu > a, .help-menu > a {
 	min-width: 0;
 }
 #sidebar-button > a {
-	background-color: var(--sidebar-background-color);
+	border: solid 1px transparent;
+	border-radius: var(--button-border-radius);
+	background-color: var(--button-background-color);
 	width: 33px;
 }
-#sidebar-button > a:hover, #sidebar-button > a:focus-visible {
-	background-color: var(--main-background-color);
+.src #sidebar-button > a {
+	background-color: var(--sidebar-background-color);
+	border-color: var(--border-color);
 }
 
-#settings-menu > a:hover, #settings-menu > a:focus-visible,
-#help-button > a:hover, #help-button > a:focus-visible,
+#search-button > a:hover, #search-button > a:focus-visible,
+.settings-menu > a:hover, .settings-menu > a:focus-visible,
+.help-menu > a:hover, #help-menu > a:focus-visible,
+#sidebar-button > a:hover, #sidebar-button > a:focus-visible,
+#copy-path:hover, #copy-path:focus-visible,
 button#toggle-all-docs:hover, button#toggle-all-docs:focus-visible {
 	border-color: var(--settings-button-border-focus);
 	text-decoration: none;
 }
 
-#settings-menu > a::before {
+#search-button > a::before {
+	/* Magnifying glass */
+	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
+		width="18" height="18" viewBox="0 0 16 16">\
+	    <circle r="5" cy="7" cx="7" style="fill:none;stroke:black;stroke-width:3"/><path \
+	    d="M14.5,14.5 12,12" style="fill:none;stroke:black;stroke-width:3;stroke-linecap:round">\
+	    </path><desc>Search</desc>\
+	    </svg>');
+	width: 18px;
+	height: 18px;
+	filter: var(--settings-menu-filter);
+}
+
+.settings-menu > a::before {
 	/* Wheel <https://www.svgrepo.com/svg/384069/settings-cog-gear> */
 	content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
 	enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg">\
-	<path d="M10.25,6c0-0.1243286-0.0261841-0.241333-0.0366211-0.362915l1.6077881-1.5545654l\
-	-1.25-2.1650391  c0,0-1.2674561,0.3625488-2.1323853,0.6099854c-0.2034912-0.1431885-0.421875\
-	-0.2639771-0.6494751-0.3701782L7.25,0h-2.5 c0,0-0.3214111,1.2857666-0.5393066,2.1572876\
-	C3.9830933,2.2634888,3.7647095,2.3842773,3.5612183,2.5274658L1.428833,1.9174805 \
-	l-1.25,2.1650391c0,0,0.9641113,0.9321899,1.6077881,1.5545654C1.7761841,5.758667,\
-	1.75,5.8756714,1.75,6  s0.0261841,0.241333,0.0366211,0.362915L0.178833,7.9174805l1.25,\
-	2.1650391l2.1323853-0.6099854  c0.2034912,0.1432495,0.421875,0.2639771,0.6494751,0.3701782\
-	L4.75,12h2.5l0.5393066-2.1572876  c0.2276001-0.1062012,0.4459839-0.2269287,0.6494751\
-	-0.3701782l2.1323853,0.6099854l1.25-2.1650391L10.2133789,6.362915  C10.2238159,6.241333,\
-	10.25,6.1243286,10.25,6z M6,7.5C5.1715698,7.5,4.5,6.8284302,4.5,6S5.1715698,4.5,6,4.5S7.5\
-	,5.1715698,7.5,6  S6.8284302,7.5,6,7.5z" fill="black"/></svg>');
+	<path d="m4.75 0s-0.32117 1.286-0.53906 2.1576c-0.2276 0.1062-0.44625 \
+	0.2266-0.64974 0.36979l-2.1328-0.60938-1.25 2.1641s0.9644 0.93231 1.6081 1.5547c-0.010437 \
+	0.12158-0.036458 0.23895-0.036458 0.36328s0.026021 0.2417 0.036458 0.36328l-1.6081 \
+	1.5547 1.25 2.1641 2.1328-0.60937c0.20349 0.14325 0.42214 0.26359 0.64974 0.36979l0.53906 \
+	2.1576h2.5l0.53906-2.1576c0.2276-0.1062 0.44625-0.22654 0.64974-0.36979l2.1328 0.60937 \
+	1.25-2.1641-1.6081-1.5547c0.010437-0.12158 0.036458-0.23895 \
+	0.036458-0.36328s-0.02602-0.2417-0.03646-0.36328l1.6081-1.5547-1.25-2.1641s-1.2679 \
+	0.36194-2.1328 0.60938c-0.20349-0.14319-0.42214-0.26359-0.64974-0.36979l-0.53906-2.1576\
+	zm1.25 2.5495c1.9058-2.877e-4 3.4508 1.5447 3.4505 3.4505 2.877e-4 1.9058-1.5447 3.4508-3.4505 \
+	3.4505-1.9058 2.877e-4 -3.4508-1.5447-3.4505-3.4505-2.877e-4 -1.9058 1.5447-3.4508 \
+	3.4505-3.4505z" fill="black"/>\
+	<circle cx="6" cy="6" r="1.75" fill="none" stroke="black" stroke-width="1"/></svg>');
 	width: 18px;
 	height: 18px;
 	filter: var(--settings-menu-filter);
@@ -2067,36 +2221,51 @@ button#toggle-all-docs::before {
 	filter: var(--settings-menu-filter);
 }
 
-button#toggle-all-docs.will-expand::before {
-	/* Custom arrow icon */
-	content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
-	enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg">\
-	<path d="M2,5l4,-4l4,4M2,7l4,4l4,-4" stroke="black" fill="none" stroke-width="2px"/></svg>');
-}
-
-#help-button > a::before {
-	/* Question mark with circle */
-	content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
-	enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg" fill="none">\
-	<circle r="5.25" cx="6" cy="6" stroke-width="1.25" stroke="black"/>\
-	<text x="6" y="7" style="font:8px sans-serif;font-weight:1000" text-anchor="middle" \
-		dominant-baseline="middle" fill="black">?</text></svg>');
+.help-menu > a::before {
+	/* Question mark with "circle" */
+	content: url('data:image/svg+xml,\
+		<svg width="18" height="18" enable-background="new 0 0 12 12" fill="none" \
+		version="1.1" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"> \
+		<path d="m6.007 0.6931c2.515 0 5.074 1.908 5.074 5.335 0 3.55-2.567 5.278-5.088 \
+		5.278-2.477 0-5.001-1.742-5.001-5.3 0-3.38 2.527-5.314 5.014-5.314z" stroke="black" \
+		stroke-width="1.5"/>\
+		<path d="m5.999 7.932c0.3111 0 0.7062 0.2915 0.7062 0.7257 0 0.5458-0.3951 \
+		0.8099-0.7081 0.8099-0.2973 0-0.7023-0.266-0.7023-0.7668 0-0.4695 0.3834-0.7688 \
+		0.7042-0.7688z" fill="black"/>\
+		<path d="m4.281 3.946c0.0312-0.03057 0.06298-0.06029 0.09528-0.08916 0.4833-0.432 1.084-0.6722 \
+		1.634-0.6722 1.141 0 1.508 1.043 1.221 1.621-0.2753 0.5542-1.061 0.5065-1.273 \
+		1.595-0.05728 0.2939 0.0134 0.9812 0.0134 1.205" fill="none" stroke="black" \
+		stroke-width="1.25"/>\
+		</svg>');
 	width: 18px;
 	height: 18px;
 	filter: var(--settings-menu-filter);
 }
 
+/* design hack to cope with "Help" being far shorter than "Settings" etc */
+.help-menu > a {
+	width: 74px;
+}
+.help-menu > a > .label {
+	padding-right: 1px;
+}
+#toggle-all-docs:not(.will-expand) > .label {
+	padding-left: 1px;
+}
+
+#search-button > a::before,
 button#toggle-all-docs::before,
-#help-button > a::before,
-#settings-menu > a::before {
+.help-menu > a::before,
+.settings-menu > a::before {
 	filter: var(--settings-menu-filter);
 	margin: 8px;
 }
 
 @media not (pointer: coarse) {
+	#search-button > a:hover::before,
 	button#toggle-all-docs:hover::before,
-	#help-button > a:hover::before,
-	#settings-menu > a:hover::before {
+	.help-menu > a:hover::before,
+	.settings-menu > a:hover::before {
 		filter: var(--settings-menu-hover-filter);
 	}
 }
@@ -2122,9 +2291,9 @@ rustdoc-toolbar span.label {
 	/* sidebar resizer image */
 	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" \
 		fill="none" stroke="black">\
-		<rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
-		<circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\
-		<path d="m7.6121 3v16 M5.375 7.625h-2 m2 3h-2 m2 3h-2" stroke-width="1.25"/></svg>');
+		<rect x="1" y="2" width="20" height="18" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
+		<circle cx="4.375" cy="5.375" r="1" stroke-width=".75"/>\
+		<path d="m7.6121 4v14 M5.375 8.625h-2 m2 3h-2 m2 3h-2" stroke-width="1.25"/></svg>');
 	width: 22px;
 	height: 22px;
 }
@@ -2137,7 +2306,8 @@ rustdoc-toolbar span.label {
 	margin-left: 10px;
 	padding: 0;
 	padding-left: 2px;
-	border: 0;
+	border: solid 1px transparent;
+	border-radius: var(--button-border-radius);
 	font-size: 0;
 }
 #copy-path::before {
@@ -2159,7 +2329,7 @@ rustdoc-toolbar span.label {
 		transform: rotate(360deg);
 	}
 }
-#settings-menu.rotate > a img {
+.settings-menu.rotate > a img {
 	animation: rotating 2s linear infinite;
 }
 
@@ -2402,6 +2572,9 @@ However, it's not needed with smaller screen width because the doc/code block is
 	opacity: 0.75;
 	filter: var(--mobile-sidebar-menu-filter);
 }
+.src #sidebar-button > a:hover {
+	background: var(--main-background-color);
+}
 .sidebar-menu-toggle:hover::before,
 .sidebar-menu-toggle:active::before,
 .sidebar-menu-toggle:focus::before {
@@ -2410,8 +2583,8 @@ However, it's not needed with smaller screen width because the doc/code block is
 
 /* Media Queries */
 
-/* Make sure all the buttons line wrap at the same time */
 @media (max-width: 850px) {
+	/* Make sure all the buttons line wrap at the same time */
 	#search-tabs .count {
 		display: block;
 	}
@@ -2421,6 +2594,81 @@ However, it's not needed with smaller screen width because the doc/code block is
 	.side-by-side > div {
 		width: auto;
 	}
+
+	/* Text label takes up too much space at this size. */
+	.main-heading {
+		grid-template-areas:
+			"main-heading-breadcrumbs main-heading-toolbar"
+			"main-heading-h1 main-heading-toolbar"
+			"main-heading-sub-heading main-heading-toolbar";
+	}
+	.search-results-main-heading {
+		display: grid;
+		grid-template-areas:
+			"main-heading-breadcrumbs main-heading-toolbar"
+			"main-heading-breadcrumbs main-heading-toolbar"
+			"main-heading-h1 main-heading-toolbar";
+	}
+	rustdoc-toolbar {
+		margin-top: -10px;
+		display: grid;
+		grid-template-areas:
+			"x settings help"
+			"search summary summary";
+		grid-template-rows: 35px 1fr;
+	}
+	.search-results-main-heading rustdoc-toolbar {
+		display: grid;
+		grid-template-areas:
+			"settings help"
+			"search search";
+	}
+	.search-results-main-heading #toggle-all-docs {
+		display: none;
+	}
+	rustdoc-toolbar .settings-menu span.label,
+	rustdoc-toolbar .help-menu span.label
+	{
+		display: none;
+	}
+	rustdoc-toolbar .settings-menu {
+		grid-area: settings;
+	}
+	rustdoc-toolbar .help-menu {
+		grid-area: help;
+	}
+	rustdoc-toolbar .settings-menu {
+		grid-area: settings;
+	}
+	rustdoc-toolbar #search-button {
+		grid-area: search;
+	}
+	rustdoc-toolbar #toggle-all-docs {
+		grid-area: summary;
+	}
+	rustdoc-toolbar .settings-menu,
+	rustdoc-toolbar .help-menu {
+		height: 35px;
+	}
+	rustdoc-toolbar .settings-menu > a,
+	rustdoc-toolbar .help-menu > a {
+		border-radius: 2px;
+		text-align: center;
+		width: 34px;
+		padding: 5px 0;
+	}
+	rustdoc-toolbar .settings-menu > a:before,
+	rustdoc-toolbar .help-menu > a:before {
+		margin: 0 4px;
+	}
+	#settings.popover {
+		top: 16px;
+		--popover-arrow-offset: 58px;
+	}
+	#help.popover {
+		top: 16px;
+		--popover-arrow-offset: 16px;
+	}
 }
 
 /*
@@ -2435,7 +2683,7 @@ in src-script.js and main.js
 
 	/* When linking to an item with an `id` (for instance, by clicking a link in the sidebar,
 	   or visiting a URL with a fragment like `#method.new`, we don't want the item to be obscured
-	   by the topbar. Anything with an `id` gets scroll-margin-top equal to .mobile-topbar's size.
+	   by the topbar. Anything with an `id` gets scroll-margin-top equal to rustdoc-topbar's size.
 	*/
 	*[id] {
 		scroll-margin-top: 45px;
@@ -2451,18 +2699,32 @@ in src-script.js and main.js
 		visibility: hidden;
 	}
 
-	/* Text label takes up too much space at this size. */
-	rustdoc-toolbar span.label {
+
+	/* Pull settings and help up into the top bar. */
+	rustdoc-topbar span.label,
+	html:not(.hide-sidebar) .rustdoc:not(.src) rustdoc-toolbar .settings-menu > a,
+	html:not(.hide-sidebar) .rustdoc:not(.src) rustdoc-toolbar .help-menu > a
+	{
 		display: none;
 	}
-	#settings-menu > a, #help-button > a, button#toggle-all-docs {
+	rustdoc-topbar .settings-menu > a,
+	rustdoc-topbar .help-menu > a {
 		width: 33px;
+		line-height: 0;
+	}
+	rustdoc-topbar .settings-menu > a:hover,
+	rustdoc-topbar .help-menu > a:hover {
+		border: none;
+		background: var(--main-background-color);
+		border-radius: 0;
 	}
 	#settings.popover {
-		--popover-arrow-offset: 86px;
+		top: 32px;
+		--popover-arrow-offset: 48px;
 	}
 	#help.popover {
-		--popover-arrow-offset: 48px;
+		top: 32px;
+		--popover-arrow-offset: 12px;
 	}
 
 	.rustdoc {
@@ -2471,13 +2733,13 @@ in src-script.js and main.js
 		display: block;
 	}
 
-	main {
+	html:not(.hide-sidebar) main {
 		padding-left: 15px;
 		padding-top: 0px;
 	}
 
 	/* Hide the logo and item name from the sidebar. Those are displayed
-	   in the mobile-topbar instead. */
+	   in the rustdoc-topbar instead. */
 	.sidebar .logo-container,
 	.sidebar .location,
 	.sidebar-resizer {
@@ -2510,6 +2772,9 @@ in src-script.js and main.js
 		height: 100vh;
 		border: 0;
 	}
+	html .src main {
+		padding: 18px 0;
+	}
 	.src .search-form {
 		margin-left: 40px;
 	}
@@ -2529,9 +2794,9 @@ in src-script.js and main.js
 		left: 0;
 	}
 
-	.mobile-topbar h2 {
+	rustdoc-topbar > h2 {
 		padding-bottom: 0;
-		margin: auto 0.5em auto auto;
+		margin: auto;
 		overflow: hidden;
 		/* Rare exception to specifying font sizes in rem. Since the topbar
 		   height is specified in pixels, this also has to be specified in
@@ -2540,32 +2805,34 @@ in src-script.js and main.js
 		font-size: 24px;
 		white-space: nowrap;
 		text-overflow: ellipsis;
+		text-align: center;
 	}
 
-	.mobile-topbar .logo-container > img {
+	rustdoc-topbar .logo-container > img {
 		max-width: 35px;
 		max-height: 35px;
 		margin: 5px 0 5px 20px;
 	}
 
-	.mobile-topbar {
+	rustdoc-topbar {
 		display: flex;
 		flex-direction: row;
 		position: sticky;
 		z-index: 10;
-		font-size: 2rem;
 		height: 45px;
 		width: 100%;
 		left: 0;
 		top: 0;
 	}
 
-	.hide-sidebar .mobile-topbar {
+	.hide-sidebar rustdoc-topbar {
 		display: none;
 	}
 
 	.sidebar-menu-toggle {
-		width: 45px;
+		/* prevent flexbox shrinking */
+		width: 41px;
+		min-width: 41px;
 		border: none;
 		line-height: 0;
 	}
@@ -2591,9 +2858,13 @@ in src-script.js and main.js
 	#sidebar-button > a::before {
 		content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
 			viewBox="0 0 22 22" fill="none" stroke="black">\
-			<rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
-			<circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\
-			<path d="m3 7.375h16m0-3h-4" stroke-width="1.25"/></svg>');
+			<rect x="1" y="2" width="20" height="18" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
+			<g fill="black" stroke="none">\
+			<circle cx="4.375" cy="5.375" r="1" stroke-width=".75"/>\
+			<circle cx="17.375" cy="5.375" r="1" stroke-width=".75"/>\
+			<circle cx="14.375" cy="5.375" r="1" stroke-width=".75"/>\
+			</g>\
+			<path d="m3 8.375h16" stroke-width="1.25"/></svg>');
 		width: 22px;
 		height: 22px;
 	}
@@ -3283,7 +3554,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
 	border-bottom: 1px solid rgba(242, 151, 24, 0.3);
 }
 
-:root[data-theme="ayu"] #settings-menu > a img,
+:root[data-theme="ayu"] .settings-menu > a img,
 :root[data-theme="ayu"] #sidebar-button > a::before {
 	filter: invert(100);
 }
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 8e3d07b3a1c..4fcba5f120b 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -54,23 +54,6 @@ function showMain() {
 window.rootPath = getVar("root-path");
 window.currentCrate = getVar("current-crate");
 
-function setMobileTopbar() {
-    // FIXME: It would be nicer to generate this text content directly in HTML,
-    // but with the current code it's hard to get the right information in the right place.
-    const mobileTopbar = document.querySelector(".mobile-topbar");
-    const locationTitle = document.querySelector(".sidebar h2.location");
-    if (mobileTopbar) {
-        const mobileTitle = document.createElement("h2");
-        mobileTitle.className = "location";
-        if (hasClass(document.querySelector(".rustdoc"), "crate")) {
-            mobileTitle.innerHTML = `Crate <a href="#">${window.currentCrate}</a>`;
-        } else if (locationTitle) {
-            mobileTitle.innerHTML = locationTitle.innerHTML;
-        }
-        mobileTopbar.appendChild(mobileTitle);
-    }
-}
-
 /**
  * Gets the human-readable string for the virtual-key code of the
  * given KeyboardEvent, ev.
@@ -84,6 +67,7 @@ function setMobileTopbar() {
  * So I guess you could say things are getting pretty interoperable.
  *
  * @param {KeyboardEvent} ev
+ * @returns {string}
  */
 function getVirtualKey(ev) {
     if ("key" in ev && typeof ev.key !== "undefined") {
@@ -98,18 +82,8 @@ function getVirtualKey(ev) {
 }
 
 const MAIN_ID = "main-content";
-const SETTINGS_BUTTON_ID = "settings-menu";
 const ALTERNATIVE_DISPLAY_ID = "alternative-display";
 const NOT_DISPLAYED_ID = "not-displayed";
-const HELP_BUTTON_ID = "help-button";
-
-function getSettingsButton() {
-    return document.getElementById(SETTINGS_BUTTON_ID);
-}
-
-function getHelpButton() {
-    return document.getElementById(HELP_BUTTON_ID);
-}
 
 // Returns the current URL without any query parameter or hash.
 function getNakedUrl() {
@@ -174,7 +148,7 @@ function getNotDisplayedElem() {
  * contains the displayed element (there can be only one at the same time!). So basically, we switch
  * elements between the two `<section>` elements.
  *
- * @param {HTMLElement|null} elemToDisplay
+ * @param {Element|null} elemToDisplay
  */
 function switchDisplayedElement(elemToDisplay) {
     const el = getAlternativeDisplayElem();
@@ -239,14 +213,14 @@ function preLoadCss(cssUrl) {
         document.head.append(script);
     }
 
-    const settingsButton = getSettingsButton();
-    if (settingsButton) {
-        settingsButton.onclick = event => {
+    onEachLazy(document.querySelectorAll(".settings-menu"), settingsMenu => {
+        /** @param {MouseEvent} event */
+        settingsMenu.querySelector("a").onclick = event => {
             if (event.ctrlKey || event.altKey || event.metaKey) {
                 return;
             }
             window.hideAllModals(false);
-            addClass(getSettingsButton(), "rotate");
+            addClass(settingsMenu, "rotate");
             event.preventDefault();
             // Sending request for the CSS and the JS files at the same time so it will
             // hopefully be loaded when the JS will generate the settings content.
@@ -268,15 +242,42 @@ function preLoadCss(cssUrl) {
                 }
             }, 0);
         };
-    }
+    });
 
     window.searchState = {
         rustdocToolbar: document.querySelector("rustdoc-toolbar"),
         loadingText: "Loading search results...",
-        // This will always be an HTMLInputElement, but tsc can't see that
-        // @ts-expect-error
-        input: document.getElementsByClassName("search-input")[0],
-        outputElement: () => {
+        inputElement: () => {
+            let el = document.getElementsByClassName("search-input")[0];
+            if (!el) {
+                const out = nonnull(nonnull(window.searchState.outputElement()).parentElement);
+                const hdr = document.createElement("div");
+                hdr.className = "main-heading search-results-main-heading";
+                const params = window.searchState.getQueryStringParams();
+                const autofocusParam = params.search === "" ? "autofocus" : "";
+                hdr.innerHTML = `<nav class="sub">
+                    <form class="search-form loading">
+                        <span></span> <!-- This empty span is a hacky fix for Safari: see #93184 -->
+                        <input
+                            ${autofocusParam}
+                            class="search-input"
+                            name="search"
+                            aria-label="Run search in the documentation"
+                            autocomplete="off"
+                            spellcheck="false"
+                            placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…"
+                            type="search">
+                    </form>
+                </nav><div class="search-switcher"></div>`;
+                out.insertBefore(hdr, window.searchState.outputElement());
+                el = document.getElementsByClassName("search-input")[0];
+            }
+            if (el instanceof HTMLInputElement) {
+                return el;
+            }
+            return null;
+        },
+        containerElement: () => {
             let el = document.getElementById("search");
             if (!el) {
                 el = document.createElement("section");
@@ -285,6 +286,19 @@ function preLoadCss(cssUrl) {
             }
             return el;
         },
+        outputElement: () => {
+            const container = window.searchState.containerElement();
+            if (!container) {
+                return null;
+            }
+            let el = container.querySelector(".search-out");
+            if (!el) {
+                el = document.createElement("div");
+                el.className = "search-out";
+                container.appendChild(el);
+            }
+            return el;
+        },
         title: document.title,
         titleBeforeSearch: document.title,
         timeout: null,
@@ -303,25 +317,52 @@ function preLoadCss(cssUrl) {
             }
         },
         isDisplayed: () => {
-            const outputElement = window.searchState.outputElement();
-            return !!outputElement &&
-                !!outputElement.parentElement &&
-                outputElement.parentElement.id === ALTERNATIVE_DISPLAY_ID;
+            const container = window.searchState.containerElement();
+            if (!container) {
+                return false;
+            }
+            return !!container.parentElement && container.parentElement.id ===
+                ALTERNATIVE_DISPLAY_ID;
         },
         // Sets the focus on the search bar at the top of the page
         focus: () => {
-            window.searchState.input && window.searchState.input.focus();
+            const inputElement = window.searchState.inputElement();
+            window.searchState.showResults();
+            if (inputElement) {
+                inputElement.focus();
+                // Avoid glitch if something focuses the search button after clicking.
+                requestAnimationFrame(() => inputElement.focus());
+            }
         },
         // Removes the focus from the search bar.
         defocus: () => {
-            window.searchState.input && window.searchState.input.blur();
+            nonnull(window.searchState.inputElement()).blur();
         },
-        showResults: search => {
-            if (search === null || typeof search === "undefined") {
-                search = window.searchState.outputElement();
+        toggle: () => {
+            if (window.searchState.isDisplayed()) {
+                window.searchState.defocus();
+                window.searchState.hideResults();
+            } else {
+                window.searchState.focus();
             }
-            switchDisplayedElement(search);
+        },
+        showResults: () => {
             document.title = window.searchState.title;
+            if (window.searchState.isDisplayed()) {
+                return;
+            }
+            const search = window.searchState.containerElement();
+            switchDisplayedElement(search);
+            const btn = document.querySelector("#search-button a");
+            if (browserSupportsHistoryApi() && btn instanceof HTMLAnchorElement &&
+                window.searchState.getQueryStringParams().search === undefined
+            ) {
+                history.pushState(null, "", btn.href);
+            }
+            const btnLabel = document.querySelector("#search-button a span.label");
+            if (btnLabel) {
+                btnLabel.innerHTML = "Exit";
+            }
         },
         removeQueryParameters: () => {
             // We change the document title.
@@ -334,6 +375,10 @@ function preLoadCss(cssUrl) {
             switchDisplayedElement(null);
             // We also remove the query parameter from the URL.
             window.searchState.removeQueryParameters();
+            const btnLabel = document.querySelector("#search-button a span.label");
+            if (btnLabel) {
+                btnLabel.innerHTML = "Search";
+            }
         },
         getQueryStringParams: () => {
             /** @type {Object.<any, string>} */
@@ -348,11 +393,11 @@ function preLoadCss(cssUrl) {
             return params;
         },
         setup: () => {
-            const search_input = window.searchState.input;
+            let searchLoaded = false;
+            const search_input = window.searchState.inputElement();
             if (!search_input) {
                 return;
             }
-            let searchLoaded = false;
             // If you're browsing the nightly docs, the page might need to be refreshed for the
             // search to work because the hash of the JS scripts might have changed.
             function sendSearchForm() {
@@ -362,22 +407,101 @@ function preLoadCss(cssUrl) {
             function loadSearch() {
                 if (!searchLoaded) {
                     searchLoaded = true;
+                    window.rr_ = data => {
+                        window.searchIndex = data;
+                    };
+                    if (!window.StringdexOnload) {
+                        window.StringdexOnload = [];
+                    }
+                    window.StringdexOnload.push(() => {
+                        loadScript(
+                            // @ts-expect-error
+                            getVar("static-root-path") + getVar("search-js"),
+                            sendSearchForm,
+                        );
+                    });
                     // @ts-expect-error
-                    loadScript(getVar("static-root-path") + getVar("search-js"), sendSearchForm);
-                    loadScript(resourcePath("search-index", ".js"), sendSearchForm);
+                    loadScript(getVar("static-root-path") + getVar("stringdex-js"), sendSearchForm);
+                    loadScript(resourcePath("search.index/root", ".js"), sendSearchForm);
                 }
             }
 
             search_input.addEventListener("focus", () => {
-                window.searchState.origPlaceholder = search_input.placeholder;
-                search_input.placeholder = "Type your search here.";
                 loadSearch();
             });
 
-            if (search_input.value !== "") {
-                loadSearch();
+            const btn = document.getElementById("search-button");
+            if (btn) {
+                btn.onclick = event => {
+                    if (event.ctrlKey || event.altKey || event.metaKey) {
+                        return;
+                    }
+                    event.preventDefault();
+                    window.searchState.toggle();
+                    loadSearch();
+                };
             }
 
+            // Push and pop states are used to add search results to the browser
+            // history.
+            if (browserSupportsHistoryApi()) {
+                // Store the previous <title> so we can revert back to it later.
+                const previousTitle = document.title;
+
+                window.addEventListener("popstate", e => {
+                    const params = window.searchState.getQueryStringParams();
+                    // Revert to the previous title manually since the History
+                    // API ignores the title parameter.
+                    document.title = previousTitle;
+                    // Synchronize search bar with query string state and
+                    // perform the search. This will empty the bar if there's
+                    // nothing there, which lets you really go back to a
+                    // previous state with nothing in the bar.
+                    const inputElement = window.searchState.inputElement();
+                    if (params.search !== undefined && inputElement !== null) {
+                        loadSearch();
+                        inputElement.value = params.search;
+                        // Some browsers fire "onpopstate" for every page load
+                        // (Chrome), while others fire the event only when actually
+                        // popping a state (Firefox), which is why search() is
+                        // called both here and at the end of the startSearch()
+                        // function.
+                        e.preventDefault();
+                        window.searchState.showResults();
+                        if (params.search === "") {
+                            window.searchState.focus();
+                        }
+                    } else {
+                        // When browsing back from search results the main page
+                        // visibility must be reset.
+                        window.searchState.hideResults();
+                    }
+                });
+            }
+
+            // This is required in firefox to avoid this problem: Navigating to a search result
+            // with the keyboard, hitting enter, and then hitting back would take you back to
+            // the doc page, rather than the search that should overlay it.
+            // This was an interaction between the back-forward cache and our handlers
+            // that try to sync state between the URL and the search input. To work around it,
+            // do a small amount of re-init on page show.
+            window.onpageshow = () => {
+                const inputElement = window.searchState.inputElement();
+                const qSearch = window.searchState.getQueryStringParams().search;
+                if (qSearch !== undefined && inputElement !== null) {
+                    if (inputElement.value === "") {
+                        inputElement.value = qSearch;
+                    }
+                    window.searchState.showResults();
+                    if (qSearch === "") {
+                        loadSearch();
+                        window.searchState.focus();
+                    }
+                } else {
+                    window.searchState.hideResults();
+                }
+            };
+
             const params = window.searchState.getQueryStringParams();
             if (params.search !== undefined) {
                 window.searchState.setLoadingSearch();
@@ -386,13 +510,9 @@ function preLoadCss(cssUrl) {
         },
         setLoadingSearch: () => {
             const search = window.searchState.outputElement();
-            if (!search) {
-                return;
-            }
-            search.innerHTML = "<h3 class=\"search-loading\">" +
-                window.searchState.loadingText +
-                "</h3>";
-            window.searchState.showResults(search);
+            nonnull(search).innerHTML = "<h3 class=\"search-loading\">" +
+                window.searchState.loadingText + "</h3>";
+            window.searchState.showResults();
         },
         descShards: new Map(),
         loadDesc: async function({descShard, descIndex}) {
@@ -1155,13 +1275,11 @@ function preLoadCss(cssUrl) {
     }
 
     window.addEventListener("resize", () => {
-        // @ts-expect-error
         if (window.CURRENT_TOOLTIP_ELEMENT) {
             // As a workaround to the behavior of `contains: layout` used in doc togglers,
             // tooltip popovers are positioned using javascript.
             //
             // This means when the window is resized, we need to redo the layout.
-            // @ts-expect-error
             const base = window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE;
             const force_visible = base.TOOLTIP_FORCE_VISIBLE;
             hideTooltip(false);
@@ -1207,11 +1325,9 @@ function preLoadCss(cssUrl) {
      */
     function showTooltip(e) {
         const notable_ty = e.getAttribute("data-notable-ty");
-        // @ts-expect-error
         if (!window.NOTABLE_TRAITS && notable_ty) {
             const data = document.getElementById("notable-traits-data");
             if (data) {
-                // @ts-expect-error
                 window.NOTABLE_TRAITS = JSON.parse(data.innerText);
             } else {
                 throw new Error("showTooltip() called with notable without any notable traits!");
@@ -1219,14 +1335,15 @@ function preLoadCss(cssUrl) {
         }
         // Make this function idempotent. If the tooltip is already shown, avoid doing extra work
         // and leave it alone.
-        // @ts-expect-error
         if (window.CURRENT_TOOLTIP_ELEMENT && window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE === e) {
-            // @ts-expect-error
             clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
             return;
         }
         window.hideAllModals(false);
-        const wrapper = document.createElement("div");
+        // use Object.assign to make sure the object has the correct type
+        // with all of the correct fields before it is assigned to a variable,
+        // as typescript has no way to change the type of a variable once it is initialized.
+        const wrapper = Object.assign(document.createElement("div"), {TOOLTIP_BASE: e});
         if (notable_ty) {
             wrapper.innerHTML = "<div class=\"content\">" +
                 // @ts-expect-error
@@ -1272,11 +1389,7 @@ function preLoadCss(cssUrl) {
             );
         }
         wrapper.style.visibility = "";
-        // @ts-expect-error
         window.CURRENT_TOOLTIP_ELEMENT = wrapper;
-        // @ts-expect-error
-        window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE = e;
-        // @ts-expect-error
         clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
         wrapper.onpointerenter = ev => {
             // If this is a synthetic touch event, ignore it. A click event will be along shortly.
@@ -1311,19 +1424,15 @@ function preLoadCss(cssUrl) {
      */
     function setTooltipHoverTimeout(element, show) {
         clearTooltipHoverTimeout(element);
-        // @ts-expect-error
         if (!show && !window.CURRENT_TOOLTIP_ELEMENT) {
             // To "hide" an already hidden element, just cancel its timeout.
             return;
         }
-        // @ts-expect-error
         if (show && window.CURRENT_TOOLTIP_ELEMENT) {
             // To "show" an already visible element, just cancel its timeout.
             return;
         }
-        // @ts-expect-error
         if (window.CURRENT_TOOLTIP_ELEMENT &&
-            // @ts-expect-error
             window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE !== element) {
             // Don't do anything if another tooltip is already visible.
             return;
@@ -1346,24 +1455,20 @@ function preLoadCss(cssUrl) {
      */
     function clearTooltipHoverTimeout(element) {
         if (element.TOOLTIP_HOVER_TIMEOUT !== undefined) {
-            // @ts-expect-error
             removeClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");
             clearTimeout(element.TOOLTIP_HOVER_TIMEOUT);
             delete element.TOOLTIP_HOVER_TIMEOUT;
         }
     }
 
-    // @ts-expect-error
+    /**
+     * @param {Event & { relatedTarget: Node }} event
+     */
     function tooltipBlurHandler(event) {
-        // @ts-expect-error
         if (window.CURRENT_TOOLTIP_ELEMENT &&
-            // @ts-expect-error
             !window.CURRENT_TOOLTIP_ELEMENT.contains(document.activeElement) &&
-            // @ts-expect-error
             !window.CURRENT_TOOLTIP_ELEMENT.contains(event.relatedTarget) &&
-            // @ts-expect-error
             !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(document.activeElement) &&
-            // @ts-expect-error
             !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(event.relatedTarget)
         ) {
             // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.
@@ -1385,30 +1490,22 @@ function preLoadCss(cssUrl) {
      *                          If set to `false`, leave keyboard focus alone.
      */
     function hideTooltip(focus) {
-        // @ts-expect-error
         if (window.CURRENT_TOOLTIP_ELEMENT) {
-            // @ts-expect-error
             if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) {
                 if (focus) {
-                    // @ts-expect-error
                     window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus();
                 }
-                // @ts-expect-error
                 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE = false;
             }
-            // @ts-expect-error
             document.body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);
-            // @ts-expect-error
             clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
-            // @ts-expect-error
-            window.CURRENT_TOOLTIP_ELEMENT = null;
+            window.CURRENT_TOOLTIP_ELEMENT = undefined;
         }
     }
 
     onEachLazy(document.getElementsByClassName("tooltip"), e => {
         e.onclick = () => {
             e.TOOLTIP_FORCE_VISIBLE = e.TOOLTIP_FORCE_VISIBLE ? false : true;
-            // @ts-expect-error
             if (window.CURRENT_TOOLTIP_ELEMENT && !e.TOOLTIP_FORCE_VISIBLE) {
                 hideTooltip(true);
             } else {
@@ -1444,9 +1541,7 @@ function preLoadCss(cssUrl) {
             if (ev.pointerType !== "mouse") {
                 return;
             }
-            // @ts-expect-error
             if (!e.TOOLTIP_FORCE_VISIBLE && window.CURRENT_TOOLTIP_ELEMENT &&
-                // @ts-expect-error
                 !window.CURRENT_TOOLTIP_ELEMENT.contains(ev.relatedTarget)) {
                 // Tooltip pointer leave gesture:
                 //
@@ -1479,7 +1574,6 @@ function preLoadCss(cssUrl) {
                 // * https://www.nngroup.com/articles/tooltip-guidelines/
                 // * https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown
                 setTooltipHoverTimeout(e, false);
-                // @ts-expect-error
                 addClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");
             }
         };
@@ -1500,15 +1594,13 @@ function preLoadCss(cssUrl) {
 
     // @ts-expect-error
     function helpBlurHandler(event) {
-        // @ts-expect-error
-        if (!getHelpButton().contains(document.activeElement) &&
-            // @ts-expect-error
-            !getHelpButton().contains(event.relatedTarget) &&
-            // @ts-expect-error
-            !getSettingsButton().contains(document.activeElement) &&
-            // @ts-expect-error
-            !getSettingsButton().contains(event.relatedTarget)
-        ) {
+        const isInPopover = onEachLazy(
+            document.querySelectorAll(".settings-menu, .help-menu"),
+            menu => {
+                return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);
+            },
+        );
+        if (!isInPopover) {
             window.hidePopoverMenus();
         }
     }
@@ -1529,7 +1621,7 @@ function preLoadCss(cssUrl) {
             ["&#9166;", "Go to active search result"],
             ["+", "Expand all sections"],
             ["-", "Collapse all sections"],
-            // for the sake of brevity, we don't say "inherint impl blocks",
+            // for the sake of brevity, we don't say "inherit impl blocks",
             // although that would be more correct,
             // since trait impl blocks are collapsed by -
             ["_", "Collapse all sections, including impl blocks"],
@@ -1571,10 +1663,9 @@ function preLoadCss(cssUrl) {
 
         const container = document.createElement("div");
         if (!isHelpPage) {
-            container.className = "popover";
+            container.className = "popover content";
         }
         container.id = "help";
-        container.style.display = "none";
 
         const side_by_side = document.createElement("div");
         side_by_side.className = "side-by-side";
@@ -1588,19 +1679,17 @@ function preLoadCss(cssUrl) {
         if (isHelpPage) {
             const help_section = document.createElement("section");
             help_section.appendChild(container);
-            // @ts-expect-error
-            document.getElementById("main-content").appendChild(help_section);
-            container.style.display = "block";
+            nonnull(document.getElementById("main-content")).appendChild(help_section);
         } else {
-            const help_button = getHelpButton();
-            // @ts-expect-error
-            help_button.appendChild(container);
-
-            container.onblur = helpBlurHandler;
-            // @ts-expect-error
-            help_button.onblur = helpBlurHandler;
-            // @ts-expect-error
-            help_button.children[0].onblur = helpBlurHandler;
+            onEachLazy(document.getElementsByClassName("help-menu"), menu => {
+                if (menu.offsetWidth !== 0) {
+                    menu.appendChild(container);
+                    container.onblur = helpBlurHandler;
+                    menu.onblur = helpBlurHandler;
+                    menu.children[0].onblur = helpBlurHandler;
+                    return true;
+                }
+            });
         }
 
         return container;
@@ -1621,80 +1710,57 @@ function preLoadCss(cssUrl) {
      * Hide all the popover menus.
      */
     window.hidePopoverMenus = () => {
-        onEachLazy(document.querySelectorAll("rustdoc-toolbar .popover"), elem => {
+        onEachLazy(document.querySelectorAll(".settings-menu .popover"), elem => {
             elem.style.display = "none";
         });
-        const button = getHelpButton();
-        if (button) {
-            removeClass(button, "help-open");
-        }
+        onEachLazy(document.querySelectorAll(".help-menu .popover"), elem => {
+            elem.parentElement.removeChild(elem);
+        });
     };
 
     /**
-     * Returns the help menu element (not the button).
-     *
-     * @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be
-     *                                built if it doesn't exist.
-     *
-     * @return {HTMLElement}
-     */
-    function getHelpMenu(buildNeeded) {
-        // @ts-expect-error
-        let menu = getHelpButton().querySelector(".popover");
-        if (!menu && buildNeeded) {
-            menu = buildHelpMenu();
-        }
-        // @ts-expect-error
-        return menu;
-    }
-
-    /**
      * Show the help popup menu.
      */
     function showHelp() {
+        window.hideAllModals(false);
         // Prevent `blur` events from being dispatched as a result of closing
         // other modals.
-        const button = getHelpButton();
-        addClass(button, "help-open");
-        // @ts-expect-error
-        button.querySelector("a").focus();
-        const menu = getHelpMenu(true);
-        if (menu.style.display === "none") {
-            // @ts-expect-error
-            window.hideAllModals();
-            menu.style.display = "";
-        }
+        onEachLazy(document.querySelectorAll(".help-menu a"), menu => {
+            if (menu.offsetWidth !== 0) {
+                menu.focus();
+                return true;
+            }
+        });
+        buildHelpMenu();
     }
 
-    const helpLink = document.querySelector(`#${HELP_BUTTON_ID} > a`);
     if (isHelpPage) {
         buildHelpMenu();
-    } else if (helpLink) {
-        helpLink.addEventListener("click", event => {
-            // By default, have help button open docs in a popover.
-            // If user clicks with a moderator, though, use default browser behavior,
-            // probably opening in a new window or tab.
-            if (!helpLink.contains(helpLink) ||
-                // @ts-expect-error
-                event.ctrlKey ||
-                // @ts-expect-error
-                event.altKey ||
-                // @ts-expect-error
-                event.metaKey) {
-                return;
-            }
-            event.preventDefault();
-            const menu = getHelpMenu(true);
-            const shouldShowHelp = menu.style.display === "none";
-            if (shouldShowHelp) {
-                showHelp();
-            } else {
-                window.hidePopoverMenus();
-            }
+    } else {
+        onEachLazy(document.querySelectorAll(".help-menu > a"), helpLink => {
+            helpLink.addEventListener(
+                "click",
+                /** @param {MouseEvent} event */
+                event => {
+                    // By default, have help button open docs in a popover.
+                    // If user clicks with a moderator, though, use default browser behavior,
+                    // probably opening in a new window or tab.
+                    if (event.ctrlKey ||
+                        event.altKey ||
+                        event.metaKey) {
+                        return;
+                    }
+                    event.preventDefault();
+                    if (document.getElementById("help")) {
+                        window.hidePopoverMenus();
+                    } else {
+                        showHelp();
+                    }
+                },
+            );
         });
     }
 
-    setMobileTopbar();
     addSidebarItems();
     addSidebarCrates();
     onHashChange(null);
@@ -1746,13 +1812,20 @@ function preLoadCss(cssUrl) {
     // On larger, "desktop-sized" viewports (though that includes many
     // tablets), it's fixed-position, appears in the left side margin,
     // and it can be activated by resizing the sidebar into nothing.
-    const sidebarButton = document.getElementById("sidebar-button");
+    let sidebarButton = document.getElementById("sidebar-button");
+    const body = document.querySelector(".main-heading");
+    if (!sidebarButton && body) {
+        sidebarButton = document.createElement("div");
+        sidebarButton.id = "sidebar-button";
+        const path = `${window.rootPath}${window.currentCrate}/all.html`;
+        sidebarButton.innerHTML = `<a href="${path}" title="show sidebar"></a>`;
+        body.insertBefore(sidebarButton, body.firstChild);
+    }
     if (sidebarButton) {
         sidebarButton.addEventListener("click", e => {
             removeClass(document.documentElement, "hide-sidebar");
             updateLocalStorage("hide-sidebar", "false");
-            if (document.querySelector(".rustdoc.src")) {
-                // @ts-expect-error
+            if (window.rustdocToggleSrcSidebar) {
                 window.rustdocToggleSrcSidebar();
             }
             e.preventDefault();
diff --git a/src/librustdoc/html/static/js/rustdoc.d.ts b/src/librustdoc/html/static/js/rustdoc.d.ts
index 3d30a7adb98..3ac10742e41 100644
--- a/src/librustdoc/html/static/js/rustdoc.d.ts
+++ b/src/librustdoc/html/static/js/rustdoc.d.ts
@@ -2,6 +2,8 @@
 // not put into the JavaScript we include as part of the documentation. It is used for
 // type checking. See README.md in this directory for more info.
 
+import { RoaringBitmap } from "./stringdex";
+
 /* eslint-disable */
 declare global {
     /** Search engine data used by main.js and search.js */
@@ -10,11 +12,25 @@ declare global {
     declare function nonnull(x: T|null, msg: string|undefined);
     /** Defined and documented in `storage.js` */
     declare function nonundef(x: T|undefined, msg: string|undefined);
+    interface PromiseConstructor {
+        /**
+         * Polyfill
+         * @template T
+         */
+        withResolvers: function(): {
+            "promise": Promise<T>,
+            "resolve": (function(T): void),
+            "reject": (function(any): void)
+        };
+    }
     interface Window {
         /** Make the current theme easy to find */
         currentTheme: HTMLLinkElement|null;
         /** Generated in `render/context.rs` */
         SIDEBAR_ITEMS?: { [key: string]: string[] };
+        /** Notable trait data */
+        NOTABLE_TRAITS?: { [key: string]: string };
+        CURRENT_TOOLTIP_ELEMENT?: HTMLElement & { TOOLTIP_BASE: HTMLElement };
         /** Used by the popover tooltip code. */
         RUSTDOC_TOOLTIP_HOVER_MS: number;
         /** Used by the popover tooltip code. */
@@ -80,6 +96,10 @@ declare global {
         pending_type_impls?: rustdoc.TypeImpls,
         rustdoc_add_line_numbers_to_examples?: function(),
         rustdoc_remove_line_numbers_from_examples?: function(),
+        /** JSON-encoded raw search index */
+        searchIndex: string,
+        /** Used in search index shards in order to load data into the in-memory database */
+        rr_: function(string),
     }
     interface HTMLElement {
         /** Used by the popover tooltip code. */
@@ -95,29 +115,28 @@ declare namespace rustdoc {
     interface SearchState {
         rustdocToolbar: HTMLElement|null;
         loadingText: string;
-        input: HTMLInputElement|null;
+        inputElement: function(): HTMLInputElement|null;
+        containerElement: function(): Element|null;
         title: string;
         titleBeforeSearch: string;
-        timeout: number|null;
+        timeout: ReturnType<typeof setTimeout>|null;
         currentTab: number;
-        focusedByTab: [number|null, number|null, number|null];
+        focusedByTab: [Element|null, Element|null, Element|null];
         clearInputTimeout: function;
-        outputElement(): HTMLElement|null;
-        focus();
-        defocus();
-        // note: an optional param is not the same as
-        // a nullable/undef-able param.
-        showResults(elem?: HTMLElement|null);
-        removeQueryParameters();
-        hideResults();
-        getQueryStringParams(): Object.<any, string>;
-        origPlaceholder: string;
+        outputElement: function(): Element|null;
+        focus: function();
+        defocus: function();
+        toggle: function();
+        showResults: function();
+        removeQueryParameters: function();
+        hideResults: function();
+        getQueryStringParams: function(): Object.<any, string>;
         setup: function();
         setLoadingSearch();
         descShards: Map<string, SearchDescShard[]>;
         loadDesc: function({descShard: SearchDescShard, descIndex: number}): Promise<string|null>;
-        loadedDescShard(string, number, string);
-        isDisplayed(): boolean,
+        loadedDescShard: function(string, number, string);
+        isDisplayed: function(): boolean;
     }
 
     interface SearchDescShard {
@@ -131,12 +150,13 @@ declare namespace rustdoc {
      * A single parsed "atom" in a search query. For example,
      * 
      *     std::fmt::Formatter, Write -> Result<()>
-     *     ┏━━━━━━━━━━━━━━━━━━  ┌────    ┏━━━━━┅┅┅┅┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐
-     *     ┃                    │        ┗ QueryElement {        ┊
-     *     ┃                    │              name: Result      ┊
-     *     ┃                    │              generics: [       ┊
-     *     ┃                    │                   QueryElement ┘
-     *     ┃                    │                   name: ()
+     *     ┏━━━━━━━━━━━━━━━━━━  ┌────    ┏━━━━━┅┅┅┅┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐
+     *     ┃                    │        ┗ QueryElement {          ┊
+     *     ┃                    │              name: Result        ┊
+     *     ┃                    │              generics: [         ┊
+     *     ┃                    │                   QueryElement { ┘
+     *     ┃                    │                       name: ()
+     *     ┃                    │                   }
      *     ┃                    │              ]
      *     ┃                    │          }
      *     ┃                    └ QueryElement {
@@ -156,14 +176,14 @@ declare namespace rustdoc {
         normalizedPathLast: string,
         generics: Array<QueryElement>,
         bindings: Map<number, Array<QueryElement>>,
-        typeFilter: number|null,
+        typeFilter: number,
     }
 
     /**
      * Same as QueryElement, but bindings and typeFilter support strings
      */
     interface ParserQueryElement {
-        name: string|null,
+        name: string,
         id: number|null,
         fullPath: Array<string>,
         pathWithoutLast: Array<string>,
@@ -172,7 +192,7 @@ declare namespace rustdoc {
         generics: Array<ParserQueryElement>,
         bindings: Map<string, Array<ParserQueryElement>>,
         bindingName: {name: string|null, generics: ParserQueryElement[]}|null,
-        typeFilter: number|string|null,
+        typeFilter: string|null,
     }
 
     /**
@@ -215,35 +235,74 @@ declare namespace rustdoc {
     /**
      * An entry in the search index database.
      */
+    interface EntryData {
+        krate: number,
+        ty: ItemType,
+        modulePath: number?,
+        exactModulePath: number?,
+        parent: number?,
+        deprecated: boolean,
+        associatedItemDisambiguator: string?,
+    }
+
+    /**
+     * A path in the search index database
+     */
+    interface PathData {
+        ty: ItemType,
+        modulePath: string,
+        exactModulePath: string?,
+    }
+
+    /**
+     * A function signature in the search index database
+     *
+     * Note that some non-function items (eg. constants, struct fields) have a function signature so they can appear in type-based search.
+     */
+    interface FunctionData {
+        functionSignature: FunctionSearchType|null,
+        paramNames: string[],
+        elemCount: number,
+    }
+
+    /**
+     * A function signature in the search index database
+     */
+    interface TypeData {
+        searchUnbox: boolean,
+        invertedFunctionSignatureIndex: RoaringBitmap[],
+    }
+
+    /**
+     * A search entry of some sort.
+     */
     interface Row {
-        crate: string,
-        descShard: SearchDescShard,
         id: number,
-        // This is the name of the item. For doc aliases, if you want the name of the aliased
-        // item, take a look at `Row.original.name`.
+        crate: string,
+        ty: ItemType,
         name: string,
         normalizedName: string,
-        word: string,
-        paramNames: string[],
-        parent: ({ty: number, name: string, path: string, exactPath: string}|null|undefined),
-        path: string,
-        ty: number,
-        type: FunctionSearchType | null,
-        descIndex: number,
-        bitIndex: number,
-        implDisambiguator: String | null,
-        is_alias?: boolean,
-        original?: Row,
+        modulePath: string,
+        exactModulePath: string,
+        entry: EntryData?,
+        path: PathData?,
+        type: FunctionData?,
+        deprecated: boolean,
+        parent: { path: PathData, name: string}?,
     }
 
+    type ItemType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
+        11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
+        21 | 22 | 23 | 24 | 25 | 26;
+
     /**
      * The viewmodel for the search engine results page.
      */
     interface ResultsTable {
-        in_args: Array<ResultObject>,
-        returned: Array<ResultObject>,
-        others: Array<ResultObject>,
-        query: ParsedQuery,
+        in_args: AsyncGenerator<ResultObject>,
+        returned: AsyncGenerator<ResultObject>,
+        others: AsyncGenerator<ResultObject>,
+        query: ParsedQuery<rustdoc.ParserQueryElement>,
     }
 
     type Results = { max_dist?: number } & Map<number, ResultObject>
@@ -252,25 +311,41 @@ declare namespace rustdoc {
      * An annotated `Row`, used in the viewmodel.
      */
     interface ResultObject {
-        desc: string,
+        desc: Promise<string|null>,
         displayPath: string,
         fullPath: string,
         href: string,
         id: number,
         dist: number,
         path_dist: number,
-        name: string,
-        normalizedName: string,
-        word: string,
         index: number,
-        parent: (Object|undefined),
-        path: string,
-        ty: number,
+        parent: ({
+            path: string,
+            exactPath: string,
+            name: string,
+            ty: number,
+        }|undefined),
         type?: FunctionSearchType,
         paramNames?: string[],
         displayTypeSignature: Promise<rustdoc.DisplayTypeSignature> | null,
         item: Row,
-        dontValidate?: boolean,
+        is_alias: boolean,
+        alias?: string,
+    }
+
+    /**
+     * An annotated `Row`, used in the viewmodel.
+     */
+    interface PlainResultObject {
+        id: number,
+        dist: number,
+        path_dist: number,
+        index: number,
+        elems: rustdoc.QueryElement[],
+        returned: rustdoc.QueryElement[],
+        is_alias: boolean,
+        alias?: string,
+        original?: rustdoc.Rlow,
     }
 
     /**
@@ -364,7 +439,19 @@ declare namespace rustdoc {
      * Numeric IDs are *ONE-indexed* into the paths array (`p`). Zero is used as a sentinel for `null`
      * because `null` is four bytes while `0` is one byte.
      */
-    type RawFunctionType = number | [number, Array<RawFunctionType>];
+    type RawFunctionType = number | [number, Array<RawFunctionType>] | [number, Array<RawFunctionType>, Array<[RawFunctionType, RawFunctionType[]]>];
+
+    /**
+     * Utility typedef for deserializing compact JSON.
+     *
+     * R is the required part, O is the optional part, which goes afterward.
+     * For example, `ArrayWithOptionals<[A, B], [C, D]>` matches
+     * `[A, B] | [A, B, C] | [A, B, C, D]`.
+     */
+    type ArrayWithOptionals<R extends any[], O extends any[]> =
+        O extends [infer First, ...infer Rest] ?
+            R | ArrayWithOptionals<[...R, First], Rest> :
+            R;
 
     /**
      * The type signature entry in the decoded search index.
@@ -382,8 +469,8 @@ declare namespace rustdoc {
      */
     interface FunctionType {
         id: null|number,
-        ty: number|null,
-        name?: string,
+        ty: ItemType,
+        name: string|null,
         path: string|null,
         exactPath: string|null,
         unboxFlag: boolean,
@@ -403,70 +490,6 @@ declare namespace rustdoc {
         bindings: Map<number, FingerprintableType[]>;
     };
 
-    /**
-     * The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f`
-     * are arrays with the same length. `q`, `a`, and `c` use a sparse
-     * representation for compactness.
-     *
-     * `n[i]` contains the name of an item.
-     *
-     * `t[i]` contains the type of that item
-     * (as a string of characters that represent an offset in `itemTypes`).
-     *
-     * `d[i]` contains the description of that item.
-     *
-     * `q` contains the full paths of the items. For compactness, it is a set of
-     * (index, path) pairs used to create a map. If a given index `i` is
-     * not present, this indicates "same as the last index present".
-     *
-     * `i[i]` contains an item's parent, usually a module. For compactness,
-     * it is a set of indexes into the `p` array.
-     *
-     * `f` contains function signatures, or `0` if the item isn't a function.
-     * More information on how they're encoded can be found in rustc-dev-guide
-     *
-     * Functions are themselves encoded as arrays. The first item is a list of
-     * types representing the function's inputs, and the second list item is a list
-     * of types representing the function's output. Tuples are flattened.
-     * Types are also represented as arrays; the first item is an index into the `p`
-     * array, while the second is a list of types representing any generic parameters.
-     *
-     * b[i] contains an item's impl disambiguator. This is only present if an item
-     * is defined in an impl block and, the impl block's type has more than one associated
-     * item with the same name.
-     *
-     * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
-     * points into the n/t/d/q/i/f arrays.
-     *
-     * `doc` contains the description of the crate.
-     *
-     * `p` is a list of path/type pairs. It is used for parents and function parameters.
-     * The first item is the type, the second is the name, the third is the visible path (if any) and
-     * the fourth is the canonical path used for deduplication (if any).
-     *
-     * `r` is the canonical path used for deduplication of re-exported items.
-     * It is not used for associated items like methods (that's the fourth element
-     * of `p`) but is used for modules items like free functions.
-     *
-     * `c` is an array of item indices that are deprecated.
-     */
-    type RawSearchIndexCrate = {
-    doc: string,
-    a: { [key: string]: number[] },
-    n: Array<string>,
-    t: string,
-    D: string,
-    e: string,
-    q: Array<[number, string]>,
-    i: string,
-    f: string,
-    p: Array<[number, string] | [number, string, number] | [number, string, number, number] | [number, string, number, number, string]>,
-    b: Array<[number, String]>,
-    c: string,
-    r: Array<[number, number]>,
-    P: Array<[number, string]>,
-    };
-
     type VlqData = VlqData[] | number;
 
     /**
@@ -498,4 +521,13 @@ declare namespace rustdoc {
         options?: string[],
         default: string | boolean,
     }
+
+    /**
+     * Single element in the data-locs field of a scraped example.
+     * First field is the start and end char index,
+     * other fields seem to be unused.
+     *
+     * Generated by `render_call_locations` in `render/mod.rs`.
+     */
+    type ScrapedLoc = [[number, number], string, string]
 }
diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js
index d641405c875..eeab591bcd8 100644
--- a/src/librustdoc/html/static/js/scrape-examples.js
+++ b/src/librustdoc/html/static/js/scrape-examples.js
@@ -1,7 +1,4 @@
-/* global addClass, hasClass, removeClass, onEachLazy */
-
-// Eventually fix this.
-// @ts-nocheck
+ /* global addClass, hasClass, removeClass, onEachLazy, nonnull */
 
 "use strict";
 
@@ -14,8 +11,16 @@
     const DEFAULT_MAX_LINES = 5;
     const HIDDEN_MAX_LINES = 10;
 
-    // Scroll code block to the given code location
+    /**
+     * Scroll code block to the given code location
+     * @param {HTMLElement} elt
+     * @param {[number, number]} loc
+     * @param {boolean} isHidden
+     */
     function scrollToLoc(elt, loc, isHidden) {
+        /** @type {HTMLElement[]} */
+        // blocked on https://github.com/microsoft/TypeScript/issues/29037
+        // @ts-expect-error
         const lines = elt.querySelectorAll("[data-nosnippet]");
         let scrollOffset;
 
@@ -35,10 +40,15 @@
             scrollOffset = offsetMid - halfHeight;
         }
 
-        lines[0].parentElement.scrollTo(0, scrollOffset);
-        elt.querySelector(".rust").scrollTo(0, scrollOffset);
+        nonnull(lines[0].parentElement).scrollTo(0, scrollOffset);
+        nonnull(elt.querySelector(".rust")).scrollTo(0, scrollOffset);
     }
 
+    /**
+     * @param {HTMLElement} parent
+     * @param {string} className
+     * @param {string} content
+     */
     function createScrapeButton(parent, className, content) {
         const button = document.createElement("button");
         button.className = className;
@@ -50,20 +60,24 @@
     window.updateScrapedExample = (example, buttonHolder) => {
         let locIndex = 0;
         const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight"));
-        const link = example.querySelector(".scraped-example-title a");
+
+        /** @type {HTMLAnchorElement} */
+        const link = nonnull(example.querySelector(".scraped-example-title a"));
         let expandButton = null;
 
         if (!example.classList.contains("expanded")) {
             expandButton = createScrapeButton(buttonHolder, "expand", "Show all");
         }
-        const isHidden = example.parentElement.classList.contains("more-scraped-examples");
+        const isHidden = nonnull(example.parentElement).classList.contains("more-scraped-examples");
 
+        // @ts-expect-error
         const locs = example.locs;
         if (locs.length > 1) {
             const next = createScrapeButton(buttonHolder, "next", "Next usage");
             const prev = createScrapeButton(buttonHolder, "prev", "Previous usage");
 
             // Toggle through list of examples in a given file
+            /** @type {function(function(): void): void} */
             const onChangeLoc = changeIndex => {
                 removeClass(highlights[locIndex], "focus");
                 changeIndex();
@@ -106,10 +120,19 @@
         }
     };
 
+    /**
+     * Initialize the `locs` field
+     *
+     * @param {HTMLElement & {locs?: rustdoc.ScrapedLoc[]}} example
+     * @param {boolean} isHidden
+     */
     function setupLoc(example, isHidden) {
-        example.locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent);
+        const locs_str = nonnull(example.attributes.getNamedItem("data-locs")).textContent;
+        const locs =
+              JSON.parse(nonnull(nonnull(locs_str)));
+        example.locs = locs;
         // Start with the first example in view
-        scrollToLoc(example, example.locs[0][0], isHidden);
+        scrollToLoc(example, locs[0][0], isHidden);
     }
 
     const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example");
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 505652c0f4a..3fb4db3a89c 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -1,9 +1,16 @@
 // ignore-tidy-filelength
-/* global addClass, getNakedUrl, getSettingValue, getVar */
-/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
+/* global addClass, getNakedUrl, getVar, nonnull, getSettingValue */
+/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi */
 
 "use strict";
 
+/**
+ * @param {stringdex.Stringdex} Stringdex
+ * @param {typeof stringdex.RoaringBitmap} RoaringBitmap
+ * @param {stringdex.Hooks} hooks
+ */
+const initSearch = async function(Stringdex, RoaringBitmap, hooks) {
+
 // polyfill
 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced
 if (!Array.prototype.toSpliced) {
@@ -20,31 +27,65 @@ if (!Array.prototype.toSpliced) {
  *
  * @template T
  * @param {Iterable<T>} arr
- * @param {function(T): any} func
+ * @param {function(T): Promise<any>} func
  * @param {function(T): boolean} funcBtwn
  */
-function onEachBtwn(arr, func, funcBtwn) {
+async function onEachBtwnAsync(arr, func, funcBtwn) {
     let skipped = true;
     for (const value of arr) {
         if (!skipped) {
             funcBtwn(value);
         }
-        skipped = func(value);
+        skipped = await func(value);
     }
 }
 
 /**
- * Convert any `undefined` to `null`.
- *
- * @template T
- * @param {T|undefined} x
- * @returns {T|null}
+ * Allow the browser to redraw.
+ * @returns {Promise<void>}
  */
-function undef2null(x) {
-    if (x !== undefined) {
-        return x;
-    }
-    return null;
+const yieldToBrowser = typeof window !== "undefined" && window.requestIdleCallback ?
+    function() {
+        return new Promise((resolve, _reject) => {
+            window.requestIdleCallback(resolve);
+        });
+    } :
+    function() {
+        return new Promise((resolve, _reject) => {
+            setTimeout(resolve, 0);
+        });
+    };
+
+/**
+ * Promise-based timer wrapper.
+ * @param {number} ms
+ * @returns {Promise<void>}
+ */
+const timeout = function(ms) {
+    return new Promise((resolve, _reject) => {
+        setTimeout(resolve, ms);
+    });
+};
+
+if (!Promise.withResolvers) {
+    /**
+     * Polyfill
+     * @template T
+     * @returns {{
+            "promise": Promise<T>,
+            "resolve": (function(T): void),
+            "reject": (function(any): void)
+        }}
+     */
+    Promise.withResolvers = () => {
+        let resolve, reject;
+        const promise = new Promise((res, rej) => {
+          resolve = res;
+          reject = rej;
+        });
+        // @ts-expect-error
+        return {promise, resolve, reject};
+    };
 }
 
 // ==================== Core search logic begin ====================
@@ -81,13 +122,22 @@ const itemTypes = [
 ];
 
 // used for special search precedence
-const TY_PRIMITIVE = itemTypes.indexOf("primitive");
-const TY_GENERIC = itemTypes.indexOf("generic");
-const TY_IMPORT = itemTypes.indexOf("import");
-const TY_TRAIT = itemTypes.indexOf("trait");
-const TY_FN = itemTypes.indexOf("fn");
-const TY_METHOD = itemTypes.indexOf("method");
-const TY_TYMETHOD = itemTypes.indexOf("tymethod");
+/** @type {rustdoc.ItemType} */
+const TY_PRIMITIVE = 1;
+/** @type {rustdoc.ItemType} */
+const TY_GENERIC = 26;
+/** @type {rustdoc.ItemType} */
+const TY_IMPORT = 4;
+/** @type {rustdoc.ItemType} */
+const TY_TRAIT = 10;
+/** @type {rustdoc.ItemType} */
+const TY_FN = 7;
+/** @type {rustdoc.ItemType} */
+const TY_METHOD = 13;
+/** @type {rustdoc.ItemType} */
+const TY_TYMETHOD = 12;
+/** @type {rustdoc.ItemType} */
+const TY_ASSOCTYPE = 17;
 const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
 
 // Hard limit on how deep to recurse into generics when doing type-driven search.
@@ -242,7 +292,9 @@ function isEndCharacter(c) {
 }
 
 /**
- * @param {number} ty
+ * Same thing as ItemType::is_fn_like in item_type.rs
+ *
+ * @param {rustdoc.ItemType} ty
  * @returns
  */
 function isFnLikeTy(ty) {
@@ -535,6 +587,45 @@ function getNextElem(query, parserState, elems, isInGenerics) {
     /** @type {rustdoc.ParserQueryElement[]} */
     const generics = [];
 
+    /** @type {function(string, string): void} */
+    const handleRefOrPtr = (chr, name) => {
+            if (parserState.typeFilter !== null && parserState.typeFilter !== "primitive") {
+            throw [
+                "Invalid search type: primitive ",
+                chr,
+                " and ",
+                parserState.typeFilter,
+                " both specified",
+            ];
+        }
+        parserState.typeFilter = null;
+        parserState.pos += 1;
+        let c = parserState.userQuery[parserState.pos];
+        while (c === " " && parserState.pos < parserState.length) {
+            parserState.pos += 1;
+            c = parserState.userQuery[parserState.pos];
+        }
+        const generics = [];
+        const pos = parserState.pos;
+        if (parserState.userQuery.slice(pos, pos + 3) === "mut") {
+            generics.push(makePrimitiveElement("mut", { typeFilter: "keyword" }));
+            parserState.pos += 3;
+            c = parserState.userQuery[parserState.pos];
+        } else if (chr === "*" && parserState.userQuery.slice(pos, pos + 5) === "const") {
+            // make *const T parse the same as *T
+            parserState.pos += 5;
+            c = parserState.userQuery[parserState.pos];
+        }
+        while (c === " " && parserState.pos < parserState.length) {
+            parserState.pos += 1;
+            c = parserState.userQuery[parserState.pos];
+        }
+        if (!isEndCharacter(c) && parserState.pos < parserState.length) {
+            getFilteredNextElem(query, parserState, generics, isInGenerics);
+        }
+        elems.push(makePrimitiveElement(name, { generics }));
+    };
+
     skipWhitespace(parserState);
     let start = parserState.pos;
     let end;
@@ -584,36 +675,9 @@ function getNextElem(query, parserState, elems, isInGenerics) {
             elems.push(makePrimitiveElement(name, { bindingName, generics }));
         }
     } else if (parserState.userQuery[parserState.pos] === "&") {
-        if (parserState.typeFilter !== null && parserState.typeFilter !== "primitive") {
-            throw [
-                "Invalid search type: primitive ",
-                "&",
-                " and ",
-                parserState.typeFilter,
-                " both specified",
-            ];
-        }
-        parserState.typeFilter = null;
-        parserState.pos += 1;
-        let c = parserState.userQuery[parserState.pos];
-        while (c === " " && parserState.pos < parserState.length) {
-            parserState.pos += 1;
-            c = parserState.userQuery[parserState.pos];
-        }
-        const generics = [];
-        if (parserState.userQuery.slice(parserState.pos, parserState.pos + 3) === "mut") {
-            generics.push(makePrimitiveElement("mut", { typeFilter: "keyword" }));
-            parserState.pos += 3;
-            c = parserState.userQuery[parserState.pos];
-        }
-        while (c === " " && parserState.pos < parserState.length) {
-            parserState.pos += 1;
-            c = parserState.userQuery[parserState.pos];
-        }
-        if (!isEndCharacter(c) && parserState.pos < parserState.length) {
-            getFilteredNextElem(query, parserState, generics, isInGenerics);
-        }
-        elems.push(makePrimitiveElement("reference", { generics }));
+        handleRefOrPtr("&", "reference");
+    } else if (parserState.userQuery[parserState.pos] === "*") {
+        handleRefOrPtr("*", "pointer");
     } else {
         const isStringElem = parserState.userQuery[start] === "\"";
         // We handle the strings on their own mostly to make code easier to follow.
@@ -1023,6 +1087,7 @@ class VlqHexDecoder {
         this.string = string;
         this.cons = cons;
         this.offset = 0;
+        this.elemCount = 0;
         /** @type {T[]} */
         this.backrefQueue = [];
     }
@@ -1060,6 +1125,7 @@ class VlqHexDecoder {
         n = (n << 4) | (c & 0xF);
         const [sign, value] = [n & 1, n >> 1];
         this.offset += 1;
+        this.elemCount += 1;
         return sign ? -value : value;
     }
     /**
@@ -1086,1247 +1152,142 @@ class VlqHexDecoder {
         return result;
     }
 }
-class RoaringBitmap {
-    /** @param {string} str */
-    constructor(str) {
-        // https://github.com/RoaringBitmap/RoaringFormatSpec
-        //
-        // Roaring bitmaps are used for flags that can be kept in their
-        // compressed form, even when loaded into memory. This decoder
-        // turns the containers into objects, but uses byte array
-        // slices of the original format for the data payload.
-        const strdecoded = atob(str);
-        const u8array = new Uint8Array(strdecoded.length);
-        for (let j = 0; j < strdecoded.length; ++j) {
-            u8array[j] = strdecoded.charCodeAt(j);
-        }
-        const has_runs = u8array[0] === 0x3b;
-        const size = has_runs ?
-            ((u8array[2] | (u8array[3] << 8)) + 1) :
-            ((u8array[4] | (u8array[5] << 8) | (u8array[6] << 16) | (u8array[7] << 24)));
-        let i = has_runs ? 4 : 8;
-        let is_run;
-        if (has_runs) {
-            const is_run_len = Math.floor((size + 7) / 8);
-            is_run = u8array.slice(i, i + is_run_len);
-            i += is_run_len;
-        } else {
-            is_run = new Uint8Array();
-        }
-        this.keys = [];
-        this.cardinalities = [];
-        for (let j = 0; j < size; ++j) {
-            this.keys.push(u8array[i] | (u8array[i + 1] << 8));
-            i += 2;
-            this.cardinalities.push((u8array[i] | (u8array[i + 1] << 8)) + 1);
-            i += 2;
-        }
-        this.containers = [];
-        let offsets = null;
-        if (!has_runs || this.keys.length >= 4) {
-            offsets = [];
-            for (let j = 0; j < size; ++j) {
-                offsets.push(u8array[i] | (u8array[i + 1] << 8) | (u8array[i + 2] << 16) |
-                    (u8array[i + 3] << 24));
-                i += 4;
-            }
-        }
-        for (let j = 0; j < size; ++j) {
-            if (offsets && offsets[j] !== i) {
-                // eslint-disable-next-line no-console
-                console.log(this.containers);
-                throw new Error(`corrupt bitmap ${j}: ${i} / ${offsets[j]}`);
-            }
-            if (is_run[j >> 3] & (1 << (j & 0x7))) {
-                const runcount = (u8array[i] | (u8array[i + 1] << 8));
-                i += 2;
-                this.containers.push(new RoaringBitmapRun(
-                    runcount,
-                    u8array.slice(i, i + (runcount * 4)),
-                ));
-                i += runcount * 4;
-            } else if (this.cardinalities[j] >= 4096) {
-                this.containers.push(new RoaringBitmapBits(u8array.slice(i, i + 8192)));
-                i += 8192;
-            } else {
-                const end = this.cardinalities[j] * 2;
-                this.containers.push(new RoaringBitmapArray(
-                    this.cardinalities[j],
-                    u8array.slice(i, i + end),
-                ));
-                i += end;
-            }
-        }
-    }
-    /** @param {number} keyvalue */
-    contains(keyvalue) {
-        const key = keyvalue >> 16;
-        const value = keyvalue & 0xFFFF;
-        // Binary search algorithm copied from
-        // https://en.wikipedia.org/wiki/Binary_search#Procedure
-        //
-        // Format is required by specification to be sorted.
-        // Because keys are 16 bits and unique, length can't be
-        // bigger than 2**16, and because we have 32 bits of safe int,
-        // left + right can't overflow.
-        let left = 0;
-        let right = this.keys.length - 1;
-        while (left <= right) {
-            const mid = Math.floor((left + right) / 2);
-            const x = this.keys[mid];
-            if (x < key) {
-                left = mid + 1;
-            } else if (x > key) {
-                right = mid - 1;
-            } else {
-                return this.containers[mid].contains(value);
-            }
-        }
-        return false;
-    }
-}
 
-class RoaringBitmapRun {
-    /**
-     * @param {number} runcount
-     * @param {Uint8Array} array
-     */
-    constructor(runcount, array) {
-        this.runcount = runcount;
-        this.array = array;
-    }
-    /** @param {number} value */
-    contains(value) {
-        // Binary search algorithm copied from
-        // https://en.wikipedia.org/wiki/Binary_search#Procedure
-        //
-        // Since runcount is stored as 16 bits, left + right
-        // can't overflow.
-        let left = 0;
-        let right = this.runcount - 1;
-        while (left <= right) {
-            const mid = Math.floor((left + right) / 2);
-            const i = mid * 4;
-            const start = this.array[i] | (this.array[i + 1] << 8);
-            const lenm1 = this.array[i + 2] | (this.array[i + 3] << 8);
-            if ((start + lenm1) < value) {
-                left = mid + 1;
-            } else if (start > value) {
-                right = mid - 1;
-            } else {
-                return true;
-            }
-        }
-        return false;
-    }
-}
-class RoaringBitmapArray {
-    /**
-     * @param {number} cardinality
-     * @param {Uint8Array} array
-     */
-    constructor(cardinality, array) {
-        this.cardinality = cardinality;
-        this.array = array;
-    }
-    /** @param {number} value */
-    contains(value) {
-        // Binary search algorithm copied from
-        // https://en.wikipedia.org/wiki/Binary_search#Procedure
-        //
-        // Since cardinality can't be higher than 4096, left + right
-        // cannot overflow.
-        let left = 0;
-        let right = this.cardinality - 1;
-        while (left <= right) {
-            const mid = Math.floor((left + right) / 2);
-            const i = mid * 2;
-            const x = this.array[i] | (this.array[i + 1] << 8);
-            if (x < value) {
-                left = mid + 1;
-            } else if (x > value) {
-                right = mid - 1;
-            } else {
-                return true;
-            }
-        }
-        return false;
-    }
-}
-class RoaringBitmapBits {
-    /**
-     * @param {Uint8Array} array
-     */
-    constructor(array) {
-        this.array = array;
-    }
-    /** @param {number} value */
-    contains(value) {
-        return !!(this.array[value >> 3] & (1 << (value & 7)));
-    }
-}
+/** @type {Array<string>} */
+const EMPTY_STRING_ARRAY = [];
+
+/** @type {Array<rustdoc.FunctionType>} */
+const EMPTY_GENERICS_ARRAY = [];
+
+/** @type {Array<[number, rustdoc.FunctionType[]]>} */
+const EMPTY_BINDINGS_ARRAY = [];
+
+/** @type {Map<number, Array<any>>} */
+const EMPTY_BINDINGS_MAP = new Map();
 
 /**
- * A prefix tree, used for name-based search.
- *
- * This data structure is used to drive prefix matches,
- * such as matching the query "link" to `LinkedList`,
- * and Lev-distance matches, such as matching the
- * query "hahsmap" to `HashMap`. Substring matches,
- * such as "list" to `LinkedList`, are done with a
- * tailTable that deep-links into this trie.
- *
- * children
- * : A [sparse array] of subtrees. The array index
- *   is a charCode.
- *
- *   [sparse array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/
- *     Indexed_collections#sparse_arrays
- *
- * matches
- * : A list of search index IDs for this node.
- *
- * @type {{
- *     children: NameTrie[],
- *     matches: number[],
- * }}
+ * @param {string|null} typename
+ * @returns {number}
  */
-class NameTrie {
-    constructor() {
-        this.children = [];
-        this.matches = [];
-    }
-    /**
-     * @param {string} name
-     * @param {number} id
-     * @param {Map<string, NameTrie[]>} tailTable
-     */
-    insert(name, id, tailTable) {
-        this.insertSubstring(name, 0, id, tailTable);
-    }
-    /**
-     * @param {string} name
-     * @param {number} substart
-     * @param {number} id
-     * @param {Map<string, NameTrie[]>} tailTable
-     */
-    insertSubstring(name, substart, id, tailTable) {
-        const l = name.length;
-        if (substart === l) {
-            this.matches.push(id);
-        } else {
-            const sb = name.charCodeAt(substart);
-            let child;
-            if (this.children[sb] !== undefined) {
-                child = this.children[sb];
-            } else {
-                child = new NameTrie();
-                this.children[sb] = child;
-                /** @type {NameTrie[]} */
-                let sste;
-                if (substart >= 2) {
-                    const tail = name.substring(substart - 2, substart + 1);
-                    const entry = tailTable.get(tail);
-                    if (entry !== undefined) {
-                        sste = entry;
-                    } else {
-                        sste = [];
-                        tailTable.set(tail, sste);
-                    }
-                    sste.push(child);
-                }
-            }
-            child.insertSubstring(name, substart + 1, id, tailTable);
-        }
+function itemTypeFromName(typename) {
+    if (typename === null) {
+        return NO_TYPE_FILTER;
     }
-    /**
-     * @param {string} name
-     * @param {Map<string, NameTrie[]>} tailTable
-     */
-    search(name, tailTable) {
-        const results = new Set();
-        this.searchSubstringPrefix(name, 0, results);
-        if (results.size < MAX_RESULTS && name.length >= 3) {
-            const levParams = name.length >= 6 ?
-                new Lev2TParametricDescription(name.length) :
-                new Lev1TParametricDescription(name.length);
-            this.searchLev(name, 0, levParams, results);
-            const tail = name.substring(0, 3);
-            const list = tailTable.get(tail);
-            if (list !== undefined) {
-                for (const entry of list) {
-                    entry.searchSubstringPrefix(name, 3, results);
-                }
-            }
-        }
-        return [...results];
-    }
-    /**
-     * @param {string} name
-     * @param {number} substart
-     * @param {Set<number>} results
-     */
-    searchSubstringPrefix(name, substart, results) {
-        const l = name.length;
-        if (substart === l) {
-            for (const match of this.matches) {
-                results.add(match);
-            }
-            // breadth-first traversal orders prefix matches by length
-            /** @type {NameTrie[]} */
-            let unprocessedChildren = [];
-            for (const child of this.children) {
-                if (child) {
-                    unprocessedChildren.push(child);
-                }
-            }
-            /** @type {NameTrie[]} */
-            let nextSet = [];
-            while (unprocessedChildren.length !== 0) {
-                /** @type {NameTrie} */
-                // @ts-expect-error
-                const next = unprocessedChildren.pop();
-                for (const child of next.children) {
-                    if (child) {
-                        nextSet.push(child);
-                    }
-                }
-                for (const match of next.matches) {
-                    results.add(match);
-                }
-                if (unprocessedChildren.length === 0) {
-                    const tmp = unprocessedChildren;
-                    unprocessedChildren = nextSet;
-                    nextSet = tmp;
-                }
-            }
-        } else {
-            const sb = name.charCodeAt(substart);
-            if (this.children[sb] !== undefined) {
-                this.children[sb].searchSubstringPrefix(name, substart + 1, results);
-            }
-        }
-    }
-    /**
-     * @param {string} name
-     * @param {number} substart
-     * @param {Lev2TParametricDescription|Lev1TParametricDescription} levParams
-     * @param {Set<number>} results
-     */
-    searchLev(name, substart, levParams, results) {
-        const stack = [[this, 0]];
-        const n = levParams.n;
-        while (stack.length !== 0) {
-            // It's not empty
-            //@ts-expect-error
-            const [trie, levState] = stack.pop();
-            for (const [charCode, child] of trie.children.entries()) {
-                if (!child) {
-                    continue;
-                }
-                const levPos = levParams.getPosition(levState);
-                const vector = levParams.getVector(
-                    name,
-                    charCode,
-                    levPos,
-                    Math.min(name.length, levPos + (2 * n) + 1),
-                );
-                const newLevState = levParams.transition(
-                    levState,
-                    levPos,
-                    vector,
-                );
-                if (newLevState >= 0) {
-                    stack.push([child, newLevState]);
-                    if (levParams.isAccept(newLevState)) {
-                        for (const match of child.matches) {
-                            results.add(match);
-                        }
-                    }
-                }
-            }
-        }
+    const index = itemTypes.findIndex(i => i === typename);
+    if (index < 0) {
+        throw ["Unknown type filter ", typename];
     }
+    return index;
 }
 
 class DocSearch {
     /**
-     * @param {Map<string, rustdoc.RawSearchIndexCrate>} rawSearchIndex
      * @param {string} rootPath
-     * @param {rustdoc.SearchState} searchState
+     * @param {stringdex.Database} database
      */
-    constructor(rawSearchIndex, rootPath, searchState) {
-        /**
-         * @type {Map<String, RoaringBitmap>}
-         */
-        this.searchIndexDeprecated = new Map();
-        /**
-         * @type {Map<String, RoaringBitmap>}
-         */
-        this.searchIndexEmptyDesc = new Map();
-        /**
-         *  @type {Uint32Array}
-         */
-        this.functionTypeFingerprint = new Uint32Array(0);
-        /**
-         * Map from normalized type names to integers. Used to make type search
-         * more efficient.
-         *
-         * @type {Map<string, {id: number, assocOnly: boolean}>}
-         */
-        this.typeNameIdMap = new Map();
-        /**
-         * Map from type ID to associated type name. Used for display,
-         * not for search.
-         *
-         * @type {Map<number, string>}
-         */
-        this.assocTypeIdNameMap = new Map();
-        this.ALIASES = new Map();
-        this.FOUND_ALIASES = new Set();
+    constructor(rootPath, database) {
         this.rootPath = rootPath;
-        this.searchState = searchState;
-
-        /**
-         * Special type name IDs for searching by array.
-         * @type {number}
-         */
-        this.typeNameIdOfArray = this.buildTypeMapIndex("array");
-        /**
-         * Special type name IDs for searching by slice.
-         * @type {number}
-         */
-        this.typeNameIdOfSlice = this.buildTypeMapIndex("slice");
-        /**
-         * Special type name IDs for searching by both array and slice (`[]` syntax).
-         * @type {number}
-         */
-        this.typeNameIdOfArrayOrSlice = this.buildTypeMapIndex("[]");
-        /**
-         * Special type name IDs for searching by tuple.
-         * @type {number}
-         */
-        this.typeNameIdOfTuple = this.buildTypeMapIndex("tuple");
-        /**
-         * Special type name IDs for searching by unit.
-         * @type {number}
-         */
-        this.typeNameIdOfUnit = this.buildTypeMapIndex("unit");
-        /**
-         * Special type name IDs for searching by both tuple and unit (`()` syntax).
-         * @type {number}
-         */
-        this.typeNameIdOfTupleOrUnit = this.buildTypeMapIndex("()");
-        /**
-         * Special type name IDs for searching `fn`.
-         * @type {number}
-         */
-        this.typeNameIdOfFn = this.buildTypeMapIndex("fn");
-        /**
-         * Special type name IDs for searching `fnmut`.
-         * @type {number}
-         */
-        this.typeNameIdOfFnMut = this.buildTypeMapIndex("fnmut");
-        /**
-         * Special type name IDs for searching `fnonce`.
-         * @type {number}
-         */
-        this.typeNameIdOfFnOnce = this.buildTypeMapIndex("fnonce");
-        /**
-         * Special type name IDs for searching higher order functions (`->` syntax).
-         * @type {number}
-         */
-        this.typeNameIdOfHof = this.buildTypeMapIndex("->");
-        /**
-         * Special type name IDs the output assoc type.
-         * @type {number}
-         */
-        this.typeNameIdOfOutput = this.buildTypeMapIndex("output", true);
-        /**
-         * Special type name IDs for searching by reference.
-         * @type {number}
-         */
-        this.typeNameIdOfReference = this.buildTypeMapIndex("reference");
-
-        /**
-         * Empty, immutable map used in item search types with no bindings.
-         *
-         * @type {Map<number, Array<any>>}
-         */
-        this.EMPTY_BINDINGS_MAP = new Map();
-
-        /**
-         * Empty, immutable map used in item search types with no bindings.
-         *
-         * @type {Array<any>}
-         */
-        this.EMPTY_GENERICS_ARRAY = [];
-
-        /**
-         * Object pool for function types with no bindings or generics.
-         * This is reset after loading the index.
-         *
-         * @type {Map<number|null, rustdoc.FunctionType>}
-         */
+        this.database = database;
+
+        this.typeNameIdOfOutput = -1;
+        this.typeNameIdOfArray = -1;
+        this.typeNameIdOfSlice = -1;
+        this.typeNameIdOfArrayOrSlice = -1;
+        this.typeNameIdOfTuple = -1;
+        this.typeNameIdOfUnit = -1;
+        this.typeNameIdOfTupleOrUnit = -1;
+        this.typeNameIdOfReference = -1;
+        this.typeNameIdOfPointer = -1;
+        this.typeNameIdOfHof = -1;
+
+        this.utf8decoder = new TextDecoder();
+
+        /** @type {Map<number|null, rustdoc.FunctionType>} */
         this.TYPES_POOL = new Map();
-
-        /**
-         * A trie for finding items by name.
-         * This is used for edit distance and prefix finding.
-         *
-         * @type {NameTrie}
-         */
-        this.nameTrie = new NameTrie();
-
-        /**
-         * Find items by 3-substring. This is a map from three-char
-         * prefixes into lists of subtries.
-         */
-        this.tailTable = new Map();
-
-        /**
-         *  @type {Array<rustdoc.Row>}
-         */
-        this.searchIndex = this.buildIndex(rawSearchIndex);
     }
 
     /**
-     * Add an item to the type Name->ID map, or, if one already exists, use it.
-     * Returns the number. If name is "" or null, return null (pure generic).
-     *
-     * This is effectively string interning, so that function matching can be
-     * done more quickly. Two types with the same name but different item kinds
-     * get the same ID.
-     *
-     * @template T extends string
-     * @overload
-     * @param {T} name
-     * @param {boolean=} isAssocType - True if this is an assoc type
-     * @returns {T extends "" ? null : number}
-     *
-     * @param {string} name
-     * @param {boolean=} isAssocType
-     * @returns {number | null}
-     *
+     * Load search index. If you do not call this function, `execQuery`
+     * will never fulfill.
      */
-    buildTypeMapIndex(name, isAssocType) {
-        if (name === "" || name === null) {
-            return null;
-        }
-
-        const obj = this.typeNameIdMap.get(name);
-        if (obj !== undefined) {
-            obj.assocOnly = !!(isAssocType && obj.assocOnly);
-            return obj.id;
-        } else {
-            const id = this.typeNameIdMap.size;
-            this.typeNameIdMap.set(name, { id, assocOnly: !!isAssocType });
-            return id;
-        }
-    }
-
-    /**
-     * Convert a list of RawFunctionType / ID to object-based FunctionType.
-     *
-     * Crates often have lots of functions in them, and it's common to have a large number of
-     * functions that operate on a small set of data types, so the search index compresses them
-     * by encoding function parameter and return types as indexes into an array of names.
-     *
-     * Even when a general-purpose compression algorithm is used, this is still a win.
-     * I checked. https://github.com/rust-lang/rust/pull/98475#issue-1284395985
-     *
-     * The format for individual function types is encoded in
-     * librustdoc/html/render/mod.rs: impl Serialize for RenderType
-     *
-     * @param {null|Array<rustdoc.RawFunctionType>} types
-     * @param {Array<{
-     *     name: string,
-    *     ty: number,
-    *     path: string|null,
-    *     exactPath: string|null,
-    *     unboxFlag: boolean
-    * }>} paths
-    * @param {Array<{
-    *     name: string,
-    *     ty: number,
-    *     path: string|null,
-    *     exactPath: string|null,
-    *     unboxFlag: boolean,
-    * }>} lowercasePaths
-     *
-     * @return {Array<rustdoc.FunctionType>}
-     */
-    buildItemSearchTypeAll(types, paths, lowercasePaths) {
-        return types && types.length > 0 ?
-            types.map(type => this.buildItemSearchType(type, paths, lowercasePaths)) :
-            this.EMPTY_GENERICS_ARRAY;
-    }
-
-    /**
-     * Converts a single type.
-     *
-     * @param {rustdoc.RawFunctionType} type
-     * @param {Array<{
-     *     name: string,
-     *     ty: number,
-     *     path: string|null,
-     *     exactPath: string|null,
-     *     unboxFlag: boolean
-     * }>} paths
-     * @param {Array<{
-     *     name: string,
-     *     ty: number,
-     *     path: string|null,
-     *     exactPath: string|null,
-     *     unboxFlag: boolean,
-     * }>} lowercasePaths
-     * @param {boolean=} isAssocType
-     */
-    buildItemSearchType(type, paths, lowercasePaths, isAssocType) {
-        const PATH_INDEX_DATA = 0;
-        const GENERICS_DATA = 1;
-        const BINDINGS_DATA = 2;
-        let pathIndex, generics, bindings;
-        if (typeof type === "number") {
-            pathIndex = type;
-            generics = this.EMPTY_GENERICS_ARRAY;
-            bindings = this.EMPTY_BINDINGS_MAP;
-        } else {
-            pathIndex = type[PATH_INDEX_DATA];
-            generics = this.buildItemSearchTypeAll(
-                type[GENERICS_DATA],
-                paths,
-                lowercasePaths,
-            );
-            // @ts-expect-error
-            if (type.length > BINDINGS_DATA && type[BINDINGS_DATA].length > 0) {
-                // @ts-expect-error
-                bindings = new Map(type[BINDINGS_DATA].map(binding => {
-                    const [assocType, constraints] = binding;
-                    // Associated type constructors are represented sloppily in rustdoc's
-                    // type search, to make the engine simpler.
-                    //
-                    // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T>
-                    // and both are, essentially
-                    // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there.
-                    // It's more like the value of a type binding is naturally an array,
-                    // which rustdoc calls "constraints".
-                    //
-                    // As a result, the key should never have generics on it.
-                    return [
-                        this.buildItemSearchType(assocType, paths, lowercasePaths, true).id,
-                        this.buildItemSearchTypeAll(constraints, paths, lowercasePaths),
-                    ];
-                }));
-            } else {
-                bindings = this.EMPTY_BINDINGS_MAP;
-            }
+    async buildIndex() {
+        const nn = this.database.getIndex("normalizedName");
+        if (!nn) {
+            return;
         }
+        // Each of these identifiers are used specially by
+        // type-driven search.
+        const [
+            // output is the special associated type that goes
+            // after the arrow: the type checker desugars
+            // the path `Fn(a) -> b` into `Fn<Output=b, (a)>`
+            output,
+            // fn, fnmut, and fnonce all match `->`
+            fn,
+            fnMut,
+            fnOnce,
+            hof,
+            // array and slice both match `[]`
+            array,
+            slice,
+            arrayOrSlice,
+            // tuple and unit both match `()`
+            tuple,
+            unit,
+            tupleOrUnit,
+            // reference matches `&`
+            reference,
+            pointer,
+            // never matches `!`
+            never,
+        ] = await Promise.all([
+            nn.search("output"),
+            nn.search("fn"),
+            nn.search("fnmut"),
+            nn.search("fnonce"),
+            nn.search("->"),
+            nn.search("array"),
+            nn.search("slice"),
+            nn.search("[]"),
+            nn.search("tuple"),
+            nn.search("unit"),
+            nn.search("()"),
+            nn.search("reference"),
+            nn.search("pointer"),
+            nn.search("never"),
+        ]);
         /**
-         * @type {rustdoc.FunctionType}
-         */
-        let result;
-        if (pathIndex < 0) {
-            // types less than 0 are generic parameters
-            // the actual names of generic parameters aren't stored, since they aren't API
-            result = {
-                id: pathIndex,
-                name: "",
-                ty: TY_GENERIC,
-                path: null,
-                exactPath: null,
-                generics,
-                bindings,
-                unboxFlag: true,
-            };
-        } else if (pathIndex === 0) {
-            // `0` is used as a sentinel because it's fewer bytes than `null`
-            result = {
-                id: null,
-                name: "",
-                ty: null,
-                path: null,
-                exactPath: null,
-                generics,
-                bindings,
-                unboxFlag: true,
-            };
-        } else {
-            const item = lowercasePaths[pathIndex - 1];
-            const id = this.buildTypeMapIndex(item.name, isAssocType);
-            if (isAssocType && id !== null) {
-                this.assocTypeIdNameMap.set(id, paths[pathIndex - 1].name);
-            }
-            result = {
-                id,
-                name: paths[pathIndex - 1].name,
-                ty: item.ty,
-                path: item.path,
-                exactPath: item.exactPath,
-                generics,
-                bindings,
-                unboxFlag: item.unboxFlag,
-            };
-        }
-        const cr = this.TYPES_POOL.get(result.id);
-        if (cr) {
-            // Shallow equality check. Since this function is used
-            // to construct every type object, this should be mostly
-            // equivalent to a deep equality check, except if there's
-            // a conflict, we don't keep the old one around, so it's
-            // not a fully precise implementation of hashcons.
-            if (cr.generics.length === result.generics.length &&
-                cr.generics !== result.generics &&
-                cr.generics.every((x, i) => result.generics[i] === x)
-            ) {
-                result.generics = cr.generics;
-            }
-            if (cr.bindings.size === result.bindings.size && cr.bindings !== result.bindings) {
-                let ok = true;
-                for (const [k, v] of cr.bindings.entries()) {
-                    // @ts-expect-error
-                    const v2 = result.bindings.get(v);
-                    if (!v2) {
-                        ok = false;
-                        break;
-                    }
-                    if (v !== v2 && v.length === v2.length && v.every((x, i) => v2[i] === x)) {
-                        result.bindings.set(k, v);
-                    } else if (v !== v2) {
-                        ok = false;
-                        break;
+         * @param {stringdex.Trie|null|undefined} trie
+         * @param {rustdoc.ItemType} ty
+         * @param {string} modulePath
+         * @returns {Promise<number>}
+         * */
+        const first = async(trie, ty, modulePath) => {
+            if (trie) {
+                for (const id of trie.matches().entries()) {
+                    const pathData = await this.getPathData(id);
+                    if (pathData && pathData.ty === ty && pathData.modulePath === modulePath) {
+                        return id;
                     }
                 }
-                if (ok) {
-                    result.bindings = cr.bindings;
-                }
-            }
-            if (cr.ty === result.ty && cr.path === result.path
-                && cr.bindings === result.bindings && cr.generics === result.generics
-                && cr.ty === result.ty && cr.name === result.name
-                && cr.unboxFlag === result.unboxFlag
-            ) {
-                return cr;
             }
-        }
-        this.TYPES_POOL.set(result.id, result);
-        return result;
-    }
-
-    /**
-     * Type fingerprints allow fast, approximate matching of types.
-     *
-     * This algo creates a compact representation of the type set using a Bloom filter.
-     * This fingerprint is used three ways:
-     *
-     * - It accelerates the matching algorithm by checking the function fingerprint against the
-     *   query fingerprint. If any bits are set in the query but not in the function, it can't
-     *   match.
-     *
-     * - The fourth section has the number of items in the set.
-     *   This is the distance function, used for filtering and for sorting.
-     *
-     * [^1]: Distance is the relatively naive metric of counting the number of distinct items in
-     * the function that are not present in the query.
-     *
-     * @param {rustdoc.FingerprintableType} type - a single type
-     * @param {Uint32Array} output - write the fingerprint to this data structure: uses 128 bits
-     */
-    buildFunctionTypeFingerprint(type, output) {
-        let input = type.id;
-        // All forms of `[]`/`()`/`->` get collapsed down to one thing in the bloom filter.
-        // Differentiating between arrays and slices, if the user asks for it, is
-        // still done in the matching algorithm.
-        if (input === this.typeNameIdOfArray || input === this.typeNameIdOfSlice) {
-            input = this.typeNameIdOfArrayOrSlice;
-        }
-        if (input === this.typeNameIdOfTuple || input === this.typeNameIdOfUnit) {
-            input = this.typeNameIdOfTupleOrUnit;
-        }
-        if (input === this.typeNameIdOfFn || input === this.typeNameIdOfFnMut ||
-            input === this.typeNameIdOfFnOnce) {
-            input = this.typeNameIdOfHof;
-        }
-        /**
-         * http://burtleburtle.net/bob/hash/integer.html
-         * ~~ is toInt32. It's used before adding, so
-         * the number stays in safe integer range.
-         * @param {number} k
-         */
-        const hashint1 = k => {
-            k = (~~k + 0x7ed55d16) + (k << 12);
-            k = (k ^ 0xc761c23c) ^ (k >>> 19);
-            k = (~~k + 0x165667b1) + (k << 5);
-            k = (~~k + 0xd3a2646c) ^ (k << 9);
-            k = (~~k + 0xfd7046c5) + (k << 3);
-            return (k ^ 0xb55a4f09) ^ (k >>> 16);
-        };
-        /** @param {number} k */
-        const hashint2 = k => {
-            k = ~k + (k << 15);
-            k ^= k >>> 12;
-            k += k << 2;
-            k ^= k >>> 4;
-            k = Math.imul(k, 2057);
-            return k ^ (k >> 16);
-        };
-        if (input !== null) {
-            const h0a = hashint1(input);
-            const h0b = hashint2(input);
-            // Less Hashing, Same Performance: Building a Better Bloom Filter
-            // doi=10.1.1.72.2442
-            const h1a = ~~(h0a + Math.imul(h0b, 2));
-            const h1b = ~~(h0a + Math.imul(h0b, 3));
-            const h2a = ~~(h0a + Math.imul(h0b, 4));
-            const h2b = ~~(h0a + Math.imul(h0b, 5));
-            output[0] |= (1 << (h0a % 32)) | (1 << (h1b % 32));
-            output[1] |= (1 << (h1a % 32)) | (1 << (h2b % 32));
-            output[2] |= (1 << (h2a % 32)) | (1 << (h0b % 32));
-            // output[3] is the total number of items in the type signature
-            output[3] += 1;
-        }
-        for (const g of type.generics) {
-            this.buildFunctionTypeFingerprint(g, output);
-        }
-        /**
-         * @type {{
-         *   id: number|null,
-         *   ty: number,
-         *   generics: rustdoc.FingerprintableType[],
-         *   bindings: Map<number, rustdoc.FingerprintableType[]>
-         * }}
-         */
-        const fb = {
-            id: null,
-            ty: 0,
-            generics: this.EMPTY_GENERICS_ARRAY,
-            bindings: this.EMPTY_BINDINGS_MAP,
+            return -1;
         };
-        for (const [k, v] of type.bindings.entries()) {
-            fb.id = k;
-            fb.generics = v;
-            this.buildFunctionTypeFingerprint(fb, output);
-        }
-    }
-
-    /**
-     * Convert raw search index into in-memory search index.
-     *
-     * @param {Map<string, rustdoc.RawSearchIndexCrate>} rawSearchIndex
-     * @returns {rustdoc.Row[]}
-     */
-    buildIndex(rawSearchIndex) {
-        /**
-         * Convert from RawFunctionSearchType to FunctionSearchType.
-         *
-         * Crates often have lots of functions in them, and function signatures are sometimes
-         * complex, so rustdoc uses a pretty tight encoding for them. This function converts it
-         * to a simpler, object-based encoding so that the actual search code is more readable
-         * and easier to debug.
-         *
-         * The raw function search type format is generated using serde in
-         * librustdoc/html/render/mod.rs: IndexItemFunctionType::write_to_string
-         *
-         * @param {Array<{
-         *     name: string,
-         *     ty: number,
-         *     path: string|null,
-         *     exactPath: string|null,
-         *     unboxFlag: boolean
-         * }>} paths
-         * @param {Array<{
-         *     name: string,
-         *     ty: number,
-         *     path: string|null,
-         *     exactPath: string|null,
-         *     unboxFlag: boolean
-         * }>} lowercasePaths
-         *
-         * @return {function(rustdoc.RawFunctionSearchType): null|rustdoc.FunctionSearchType}
-         */
-        const buildFunctionSearchTypeCallback = (paths, lowercasePaths) => {
-            /**
-             * @param {rustdoc.RawFunctionSearchType} functionSearchType
-             */
-            const cb = functionSearchType => {
-                if (functionSearchType === 0) {
-                    return null;
-                }
-                const INPUTS_DATA = 0;
-                const OUTPUT_DATA = 1;
-                /** @type {rustdoc.FunctionType[]} */
-                let inputs;
-                /** @type {rustdoc.FunctionType[]} */
-                let output;
-                if (typeof functionSearchType[INPUTS_DATA] === "number") {
-                    inputs = [
-                        this.buildItemSearchType(
-                            functionSearchType[INPUTS_DATA],
-                            paths,
-                            lowercasePaths,
-                        ),
-                    ];
-                } else {
-                    inputs = this.buildItemSearchTypeAll(
-                        functionSearchType[INPUTS_DATA],
-                        paths,
-                        lowercasePaths,
-                    );
-                }
-                if (functionSearchType.length > 1) {
-                    if (typeof functionSearchType[OUTPUT_DATA] === "number") {
-                        output = [
-                            this.buildItemSearchType(
-                                functionSearchType[OUTPUT_DATA],
-                                paths,
-                                lowercasePaths,
-                            ),
-                        ];
-                    } else {
-                        output = this.buildItemSearchTypeAll(
-                            // @ts-expect-error
-                            functionSearchType[OUTPUT_DATA],
-                            paths,
-                            lowercasePaths,
-                        );
-                    }
-                } else {
-                    output = [];
-                }
-                const where_clause = [];
-                const l = functionSearchType.length;
-                for (let i = 2; i < l; ++i) {
-                    where_clause.push(typeof functionSearchType[i] === "number"
-                        // @ts-expect-error
-                        ? [this.buildItemSearchType(functionSearchType[i], paths, lowercasePaths)]
-                        : this.buildItemSearchTypeAll(
-                            // @ts-expect-error
-                            functionSearchType[i],
-                            paths,
-                            lowercasePaths,
-                        ));
-                }
-                return {
-                    inputs, output, where_clause,
-                };
-            };
-            return cb;
-        };
-
-        /** @type {rustdoc.Row[]} */
-        const searchIndex = [];
-        let currentIndex = 0;
-        let id = 0;
-
-        // Function type fingerprints are 128-bit bloom filters that are used to
-        // estimate the distance between function and query.
-        // This loop counts the number of items to allocate a fingerprint for.
-        for (const crate of rawSearchIndex.values()) {
-            // Each item gets an entry in the fingerprint array, and the crate
-            // does, too
-            id += crate.t.length + 1;
-        }
-        this.functionTypeFingerprint = new Uint32Array((id + 1) * 4);
-        // This loop actually generates the search item indexes, including
-        // normalized names, type signature objects and fingerprints, and aliases.
-        id = 0;
-
-        /** @type {Array<[string, { [key: string]: Array<number> },  number]>} */
-        const allAliases = [];
-        for (const [crate, crateCorpus] of rawSearchIndex) {
-            // a string representing the lengths of each description shard
-            // a string representing the list of function types
-            const itemDescShardDecoder = new VlqHexDecoder(crateCorpus.D, noop => {
-                /** @type {number} */
-                // @ts-expect-error
-                const n = noop;
-                return n;
-            });
-            let descShard = {
-                crate,
-                shard: 0,
-                start: 0,
-                len: itemDescShardDecoder.next(),
-                promise: null,
-                resolve: null,
-            };
-            const descShardList = [descShard];
-
-            // Deprecated items and items with no description
-            this.searchIndexDeprecated.set(crate, new RoaringBitmap(crateCorpus.c));
-            this.searchIndexEmptyDesc.set(crate, new RoaringBitmap(crateCorpus.e));
-            let descIndex = 0;
-
-            /**
-             * List of generic function type parameter names.
-             * Used for display, not for searching.
-             * @type {string[]}
-             */
-            let lastParamNames = [];
-
-            // This object should have exactly the same set of fields as the "row"
-            // object defined below. Your JavaScript runtime will thank you.
-            // https://mathiasbynens.be/notes/shapes-ics
-            let normalizedName = crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, "");
-            const crateRow = {
-                crate,
-                ty: 3, // == ExternCrate
-                name: crate,
-                path: "",
-                descShard,
-                descIndex,
-                exactPath: "",
-                desc: crateCorpus.doc,
-                parent: undefined,
-                type: null,
-                paramNames: lastParamNames,
-                id,
-                word: crate,
-                normalizedName,
-                bitIndex: 0,
-                implDisambiguator: null,
-            };
-            this.nameTrie.insert(normalizedName, id, this.tailTable);
-            id += 1;
-            searchIndex.push(crateRow);
-            currentIndex += 1;
-            // it's not undefined
-            // @ts-expect-error
-            if (!this.searchIndexEmptyDesc.get(crate).contains(0)) {
-                descIndex += 1;
-            }
-
-            // see `RawSearchIndexCrate` in `rustdoc.d.ts` for a more
-            // up to date description of these fields
-            const itemTypes = crateCorpus.t;
-            // an array of (String) item names
-            const itemNames = crateCorpus.n;
-            // an array of [(Number) item index,
-            //              (String) full path]
-            // an item whose index is not present will fall back to the previous present path
-            // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present,
-            // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11
-            const itemPaths = new Map(crateCorpus.q);
-            // An array of [(Number) item index, (Number) path index]
-            // Used to de-duplicate inlined and re-exported stuff
-            const itemReexports = new Map(crateCorpus.r);
-            // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
-            const itemParentIdxDecoder = new VlqHexDecoder(crateCorpus.i, noop => noop);
-            // a map Number, string for impl disambiguators
-            const implDisambiguator = new Map(crateCorpus.b);
-            const rawPaths = crateCorpus.p;
-            const aliases = crateCorpus.a;
-            // an array of [(Number) item index,
-            //              (String) comma-separated list of function generic param names]
-            // an item whose index is not present will fall back to the previous present path
-            const itemParamNames = new Map(crateCorpus.P);
-
-            /**
-             * @type {Array<{
-             *     name: string,
-             *     ty: number,
-             *     path: string|null,
-             *     exactPath: string|null,
-             *     unboxFlag: boolean
-             * }>}
-             */
-            const lowercasePaths = [];
-            /**
-             * @type {Array<{
-             *     name: string,
-             *     ty: number,
-             *     path: string|null,
-             *     exactPath: string|null,
-             *     unboxFlag: boolean
-             * }>}
-             */
-            const paths = [];
-
-            // a string representing the list of function types
-            const itemFunctionDecoder = new VlqHexDecoder(
-                crateCorpus.f,
-                // @ts-expect-error
-                buildFunctionSearchTypeCallback(paths, lowercasePaths),
-            );
-
-            // convert `rawPaths` entries into object form
-            // generate normalizedPaths for function search mode
-            let len = rawPaths.length;
-            let lastPath = undef2null(itemPaths.get(0));
-            for (let i = 0; i < len; ++i) {
-                const elem = rawPaths[i];
-                const ty = elem[0];
-                const name = elem[1];
-                /**
-                 * @param {2|3} idx
-                 * @param {string|null} if_null
-                 * @param {string|null} if_not_found
-                 * @returns {string|null}
-                 */
-                const elemPath = (idx, if_null, if_not_found) => {
-                    if (elem.length > idx && elem[idx] !== undefined) {
-                        const p = itemPaths.get(elem[idx]);
-                        if (p !== undefined) {
-                            return p;
-                        }
-                        return if_not_found;
-                    }
-                    return if_null;
-                };
-                const path = elemPath(2, lastPath, null);
-                const exactPath = elemPath(3, path, path);
-                const unboxFlag = elem.length > 4 && !!elem[4];
-
-                lowercasePaths.push({ ty, name: name.toLowerCase(), path, exactPath, unboxFlag });
-                paths[i] = { ty, name, path, exactPath, unboxFlag };
-            }
-
-            // Convert `item*` into an object form, and construct word indices.
-            //
-            // Before any analysis is performed, let's gather the search terms to
-            // search against apart from the rest of the data. This is a quick
-            // operation that is cached for the life of the page state so that
-            // all other search operations have access to this cached data for
-            // faster analysis operations
-            lastPath = "";
-            len = itemTypes.length;
-            let lastName = "";
-            let lastWord = "";
-            for (let i = 0; i < len; ++i) {
-                const bitIndex = i + 1;
-                if (descIndex >= descShard.len &&
-                    // @ts-expect-error
-                    !this.searchIndexEmptyDesc.get(crate).contains(bitIndex)) {
-                    descShard = {
-                        crate,
-                        shard: descShard.shard + 1,
-                        start: descShard.start + descShard.len,
-                        len: itemDescShardDecoder.next(),
-                        promise: null,
-                        resolve: null,
-                    };
-                    descIndex = 0;
-                    descShardList.push(descShard);
-                }
-                const name = itemNames[i] === "" ? lastName : itemNames[i];
-                const word = itemNames[i] === "" ? lastWord : itemNames[i].toLowerCase();
-                const pathU = itemPaths.get(i);
-                const path = pathU !== undefined ? pathU : lastPath;
-                const paramNameString = itemParamNames.get(i);
-                const paramNames = paramNameString !== undefined ?
-                    paramNameString.split(",") :
-                    lastParamNames;
-                const type = itemFunctionDecoder.next();
-                if (type !== null) {
-                    if (type) {
-                        const fp = this.functionTypeFingerprint.subarray(id * 4, (id + 1) * 4);
-                        for (const t of type.inputs) {
-                            this.buildFunctionTypeFingerprint(t, fp);
-                        }
-                        for (const t of type.output) {
-                            this.buildFunctionTypeFingerprint(t, fp);
-                        }
-                        for (const w of type.where_clause) {
-                            for (const t of w) {
-                                this.buildFunctionTypeFingerprint(t, fp);
-                            }
-                        }
-                    }
-                }
-                // This object should have exactly the same set of fields as the "crateRow"
-                // object defined above.
-                const itemParentIdx = itemParentIdxDecoder.next();
-                normalizedName = word.indexOf("_") === -1 ? word : word.replace(/_/g, "");
-                /** @type {rustdoc.Row} */
-                const row = {
-                    crate,
-                    ty: itemTypes.charCodeAt(i) - 65, // 65 = "A"
-                    name,
-                    path,
-                    descShard,
-                    descIndex,
-                    exactPath: itemReexports.has(i) ?
-                        // @ts-expect-error
-                        itemPaths.get(itemReexports.get(i)) : path,
-                    // @ts-expect-error
-                    parent: itemParentIdx > 0 ? paths[itemParentIdx - 1] : undefined,
-                    type,
-                    paramNames,
-                    id,
-                    word,
-                    normalizedName,
-                    bitIndex,
-                    implDisambiguator: undef2null(implDisambiguator.get(i)),
-                };
-                this.nameTrie.insert(normalizedName, id, this.tailTable);
-                id += 1;
-                searchIndex.push(row);
-                lastPath = row.path;
-                lastParamNames = row.paramNames;
-                // @ts-expect-error
-                if (!this.searchIndexEmptyDesc.get(crate).contains(bitIndex)) {
-                    descIndex += 1;
-                }
-                lastName = name;
-                lastWord = word;
-            }
-
-            if (aliases) {
-                // We need to add the aliases in `searchIndex` after we finished filling it
-                // to not mess up indexes.
-                allAliases.push([crate, aliases, currentIndex]);
-            }
-            currentIndex += itemTypes.length;
-            this.searchState.descShards.set(crate, descShardList);
-        }
-
-        for (const [crate, aliases, index] of allAliases) {
-            for (const [alias_name, alias_refs] of Object.entries(aliases)) {
-                if (!this.ALIASES.has(crate)) {
-                    this.ALIASES.set(crate, new Map());
-                }
-                const word = alias_name.toLowerCase();
-                const crate_alias_map = this.ALIASES.get(crate);
-                if (!crate_alias_map.has(word)) {
-                    crate_alias_map.set(word, []);
-                }
-                const aliases_map = crate_alias_map.get(word);
-
-                const normalizedName = word.indexOf("_") === -1 ? word : word.replace(/_/g, "");
-                for (const alias of alias_refs) {
-                    const originalIndex = alias + index;
-                    const original = searchIndex[originalIndex];
-                    /** @type {rustdoc.Row} */
-                    const row = {
-                        crate,
-                        name: alias_name,
-                        normalizedName,
-                        is_alias: true,
-                        ty: original.ty,
-                        type: original.type,
-                        paramNames: [],
-                        word,
-                        id,
-                        parent: undefined,
-                        original,
-                        path: "",
-                        implDisambiguator: original.implDisambiguator,
-                        // Needed to load the description of the original item.
-                        // @ts-ignore
-                        descShard: original.descShard,
-                        descIndex: original.descIndex,
-                        bitIndex: original.bitIndex,
-                    };
-                    aliases_map.push(row);
-                    this.nameTrie.insert(normalizedName, id, this.tailTable);
-                    id += 1;
-                    searchIndex.push(row);
-                }
-            }
-        }
-        // Drop the (rather large) hash table used for reusing function items
-        this.TYPES_POOL = new Map();
-        return searchIndex;
+        this.typeNameIdOfOutput = await first(output, TY_ASSOCTYPE, "");
+        this.typeNameIdOfFnPtr = await first(fn, TY_PRIMITIVE, "");
+        this.typeNameIdOfFn = await first(fn, TY_TRAIT, "core::ops");
+        this.typeNameIdOfFnMut = await first(fnMut, TY_TRAIT, "core::ops");
+        this.typeNameIdOfFnOnce = await first(fnOnce, TY_TRAIT, "core::ops");
+        this.typeNameIdOfArray = await first(array, TY_PRIMITIVE, "");
+        this.typeNameIdOfSlice = await first(slice, TY_PRIMITIVE, "");
+        this.typeNameIdOfArrayOrSlice = await first(arrayOrSlice, TY_PRIMITIVE, "");
+        this.typeNameIdOfTuple = await first(tuple, TY_PRIMITIVE, "");
+        this.typeNameIdOfUnit = await first(unit, TY_PRIMITIVE, "");
+        this.typeNameIdOfTupleOrUnit = await first(tupleOrUnit, TY_PRIMITIVE, "");
+        this.typeNameIdOfReference = await first(reference, TY_PRIMITIVE, "");
+        this.typeNameIdOfPointer = await first(pointer, TY_PRIMITIVE, "");
+        this.typeNameIdOfHof = await first(hof, TY_PRIMITIVE, "");
+        this.typeNameIdOfNever = await first(never, TY_PRIMITIVE, "");
     }
 
     /**
@@ -2343,41 +1304,6 @@ class DocSearch {
      */
     static parseQuery(userQuery) {
         /**
-         * @param {string} typename
-         * @returns {number}
-         */
-        function itemTypeFromName(typename) {
-            const index = itemTypes.findIndex(i => i === typename);
-            if (index < 0) {
-                throw ["Unknown type filter ", typename];
-            }
-            return index;
-        }
-
-        /**
-         * @param {rustdoc.ParserQueryElement} elem
-         */
-        function convertTypeFilterOnElem(elem) {
-            if (typeof elem.typeFilter === "string") {
-                let typeFilter = elem.typeFilter;
-                if (typeFilter === "const") {
-                    typeFilter = "constant";
-                }
-                elem.typeFilter = itemTypeFromName(typeFilter);
-            } else {
-                elem.typeFilter = NO_TYPE_FILTER;
-            }
-            for (const elem2 of elem.generics) {
-                convertTypeFilterOnElem(elem2);
-            }
-            for (const constraints of elem.bindings.values()) {
-                for (const constraint of constraints) {
-                    convertTypeFilterOnElem(constraint);
-                }
-            }
-        }
-
-        /**
          * Takes the user search input and returns an empty `ParsedQuery`.
          *
          * @param {string} userQuery
@@ -2437,8 +1363,7 @@ class DocSearch {
                     continue;
                 }
                 if (!foundStopChar) {
-                    /** @type String[] */
-                    let extra = [];
+                    let extra = EMPTY_STRING_ARRAY;
                     if (isLastElemGeneric(query.elems, parserState)) {
                         extra = [" after ", ">"];
                     } else if (prevIs(parserState, "\"")) {
@@ -2515,11 +1440,33 @@ class DocSearch {
 
         try {
             parseInput(query, parserState);
+
+            // Scan for invalid type filters, so that we can report the error
+            // outside the search loop.
+            /** @param {rustdoc.ParserQueryElement} elem */
+            const checkTypeFilter = elem => {
+                const ty = itemTypeFromName(elem.typeFilter);
+                if (ty === TY_GENERIC && elem.generics.length !== 0) {
+                    throw [
+                        "Generic type parameter ",
+                        elem.name,
+                        " does not accept generic parameters",
+                    ];
+                }
+                for (const generic of elem.generics) {
+                    checkTypeFilter(generic);
+                }
+                for (const constraints of elem.bindings.values()) {
+                    for (const constraint of constraints) {
+                        checkTypeFilter(constraint);
+                    }
+                }
+            };
             for (const elem of query.elems) {
-                convertTypeFilterOnElem(elem);
+                checkTypeFilter(elem);
             }
             for (const elem of query.returned) {
-                convertTypeFilterOnElem(elem);
+                checkTypeFilter(elem);
             }
         } catch (err) {
             query = newParsedQuery(userQuery);
@@ -2543,208 +1490,573 @@ class DocSearch {
     }
 
     /**
-     * Executes the parsed query and builds a {ResultsTable}.
-     *
-     * @param  {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} origParsedQuery
-     *     - The parsed user query
-     * @param  {Object} filterCrates - Crate to search in if defined
-     * @param  {string} currentCrate - Current crate, to rank results from this crate higher
-     *
-     * @return {Promise<rustdoc.ResultsTable>}
+     * @param {number} id
+     * @returns {Promise<string|null>}
      */
-    async execQuery(origParsedQuery, filterCrates, currentCrate) {
-        /** @type {rustdoc.Results} */
-        const results_others = new Map(),
-            /** @type {rustdoc.Results} */
-            results_in_args = new Map(),
-            /** @type {rustdoc.Results} */
-            results_returned = new Map();
-
-        /** @type {rustdoc.ParsedQuery<rustdoc.QueryElement>} */
-        // @ts-expect-error
-        const parsedQuery = origParsedQuery;
+    async getName(id) {
+        const ni = this.database.getData("name");
+        if (!ni) {
+            return null;
+        }
+        const name = await ni.at(id);
+        return name === undefined || name === null ? null : this.utf8decoder.decode(name);
+    }
 
-        const queryLen =
-            parsedQuery.elems.reduce((acc, next) => acc + next.pathLast.length, 0) +
-            parsedQuery.returned.reduce((acc, next) => acc + next.pathLast.length, 0);
-        const maxEditDistance = Math.floor(queryLen / 3);
-        // We reinitialize the `FOUND_ALIASES` map.
-        this.FOUND_ALIASES.clear();
+    /**
+     * @param {number} id
+     * @returns {Promise<string|null>}
+     */
+    async getDesc(id) {
+        const di = this.database.getData("desc");
+        if (!di) {
+            return null;
+        }
+        const desc = await di.at(id);
+        return desc === undefined || desc === null ? null : this.utf8decoder.decode(desc);
+    }
+
+    /**
+     * @param {number} id
+     * @returns {Promise<number|null>}
+     */
+    async getAliasTarget(id) {
+        const ai = this.database.getData("alias");
+        if (!ai) {
+            return null;
+        }
+        const bytes = await ai.at(id);
+        if (bytes === undefined || bytes === null || bytes.length === 0) {
+            return null;
+        } else {
+            /** @type {string} */
+            const encoded = this.utf8decoder.decode(bytes);
+            /** @type {number|null} */
+            const decoded = JSON.parse(encoded);
+            return decoded;
+        }
+    }
+
+    /**
+     * @param {number} id
+     * @returns {Promise<rustdoc.EntryData|null>}
+     */
+    async getEntryData(id) {
+        const ei = this.database.getData("entry");
+        if (!ei) {
+            return null;
+        }
+        const encoded = this.utf8decoder.decode(await ei.at(id));
+        if (encoded === "" || encoded === undefined || encoded === null) {
+            return null;
+        }
+        /**
+         * krate,
+         * ty,
+         * module_path,
+         * exact_module_path,
+         * parent,
+         * deprecated,
+         * associated_item_disambiguator
+         * @type {rustdoc.ArrayWithOptionals<[
+         *     number,
+         *     rustdoc.ItemType,
+         *     number,
+         *     number,
+         *     number,
+         *     number,
+         * ], [string]>}
+         */
+        const raw = JSON.parse(encoded);
+        return {
+            krate: raw[0],
+            ty: raw[1],
+            modulePath: raw[2] === 0 ? null : raw[2] - 1,
+            exactModulePath: raw[3] === 0 ? null : raw[3] - 1,
+            parent: raw[4] === 0 ? null : raw[4] - 1,
+            deprecated: raw[5] === 1 ? true : false,
+            associatedItemDisambiguator: raw.length === 6 ? null : raw[6],
+        };
+    }
 
+    /**
+     * @param {number} id
+     * @returns {Promise<rustdoc.PathData|null>}
+     */
+    async getPathData(id) {
+        const pi = this.database.getData("path");
+        if (!pi) {
+            return null;
+        }
+        const encoded = this.utf8decoder.decode(await pi.at(id));
+        if (encoded === "" || encoded === undefined || encoded === null) {
+            return null;
+        }
         /**
-         * @type {Map<string, number>}
+         * ty, module_path, exact_module_path, search_unbox, inverted_function_signature_index
+         * @type {rustdoc.ArrayWithOptionals<[rustdoc.ItemType, string], [string|0, 0|1, string]>}
          */
-        const genericSymbols = new Map();
+        const raw = JSON.parse(encoded);
+        return {
+            ty: raw[0],
+            modulePath: raw[1],
+            exactModulePath: raw[2] === 0 || raw[2] === undefined ? raw[1] : raw[2],
+        };
+    }
 
+    /**
+     * @param {number} id
+     * @returns {Promise<rustdoc.FunctionData|null>}
+     */
+    async getFunctionData(id) {
+        const fi = this.database.getData("function");
+        if (!fi) {
+            return null;
+        }
+        const encoded = this.utf8decoder.decode(await fi.at(id));
+        if (encoded === "" || encoded === undefined || encoded === null) {
+            return null;
+        }
         /**
-         * Convert names to ids in parsed query elements.
-         * This is not used for the "In Names" tab, but is used for the
-         * "In Params", "In Returns", and "In Function Signature" tabs.
-         *
-         * If there is no matching item, but a close-enough match, this
-         * function also that correction.
-         *
-         * See `buildTypeMapIndex` for more information.
-         *
-         * @param {rustdoc.QueryElement} elem
-         * @param {boolean=} isAssocType
+         * function_signature, param_names
+         * @type {[string, string[]]}
          */
-        const convertNameToId = (elem, isAssocType) => {
-            const loweredName = elem.pathLast.toLowerCase();
-            if (this.typeNameIdMap.has(loweredName) &&
-                // @ts-expect-error
-                (isAssocType || !this.typeNameIdMap.get(loweredName).assocOnly)) {
-                // @ts-expect-error
-                elem.id = this.typeNameIdMap.get(loweredName).id;
-            } else if (!parsedQuery.literalSearch) {
-                let match = null;
-                let matchDist = maxEditDistance + 1;
-                let matchName = "";
-                for (const [name, { id, assocOnly }] of this.typeNameIdMap) {
-                    const dist = Math.min(
-                        editDistance(name, loweredName, maxEditDistance),
-                        editDistance(name, elem.normalizedPathLast, maxEditDistance),
-                    );
-                    if (dist <= matchDist && dist <= maxEditDistance &&
-                        (isAssocType || !assocOnly)) {
-                        if (dist === matchDist && matchName > name) {
-                            continue;
-                        }
-                        match = id;
-                        matchDist = dist;
-                        matchName = name;
-                    }
-                }
-                if (match !== null) {
-                    parsedQuery.correction = matchName;
-                }
-                elem.id = match;
+        const raw = JSON.parse(encoded);
+
+        const parser = new VlqHexDecoder(raw[0], async functionSearchType => {
+            if (typeof functionSearchType === "number") {
+                return null;
+            }
+            const INPUTS_DATA = 0;
+            const OUTPUT_DATA = 1;
+            /** @type {Promise<rustdoc.FunctionType[]>} */
+            let inputs_;
+            /** @type {Promise<rustdoc.FunctionType[]>} */
+            let output_;
+            if (typeof functionSearchType[INPUTS_DATA] === "number") {
+                inputs_ = Promise.all([
+                    this.buildItemSearchType(functionSearchType[INPUTS_DATA]),
+                ]);
+            } else {
+                // @ts-ignore
+                inputs_ = this.buildItemSearchTypeAll(functionSearchType[INPUTS_DATA]);
             }
-            if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1
-                && elem.generics.length === 0 && elem.bindings.size === 0)
-                || elem.typeFilter === TY_GENERIC) {
-                const id = genericSymbols.get(elem.normalizedPathLast);
-                if (id !== undefined) {
-                    elem.id = id;
+            if (functionSearchType.length > 1) {
+                if (typeof functionSearchType[OUTPUT_DATA] === "number") {
+                    output_ = Promise.all([
+                        this.buildItemSearchType(functionSearchType[OUTPUT_DATA]),
+                    ]);
                 } else {
-                    elem.id = -(genericSymbols.size + 1);
-                    genericSymbols.set(elem.normalizedPathLast, elem.id);
-                }
-                if (elem.typeFilter === -1 && elem.normalizedPathLast.length >= 3) {
-                    // Silly heuristic to catch if the user probably meant
-                    // to not write a generic parameter. We don't use it,
-                    // just bring it up.
-                    const maxPartDistance = Math.floor(elem.normalizedPathLast.length / 3);
-                    let matchDist = maxPartDistance + 1;
-                    let matchName = "";
-                    for (const name of this.typeNameIdMap.keys()) {
-                        const dist = editDistance(
-                            name,
-                            elem.normalizedPathLast,
-                            maxPartDistance,
-                        );
-                        if (dist <= matchDist && dist <= maxPartDistance) {
-                            if (dist === matchDist && matchName > name) {
-                                continue;
-                            }
-                            matchDist = dist;
-                            matchName = name;
-                        }
-                    }
-                    if (matchName !== "") {
-                        parsedQuery.proposeCorrectionFrom = elem.name;
-                        parsedQuery.proposeCorrectionTo = matchName;
-                    }
+                    // @ts-expect-error
+                    output_ = this.buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA]);
                 }
-                elem.typeFilter = TY_GENERIC;
-            }
-            if (elem.generics.length > 0 && elem.typeFilter === TY_GENERIC) {
-                // Rust does not have HKT
-                parsedQuery.error = [
-                    "Generic type parameter ",
-                    elem.name,
-                    " does not accept generic parameters",
-                ];
-            }
-            for (const elem2 of elem.generics) {
-                convertNameToId(elem2);
+            } else {
+                output_ = Promise.resolve(EMPTY_GENERICS_ARRAY);
             }
-            elem.bindings = new Map(Array.from(elem.bindings.entries())
-                .map(entry => {
-                    const [name, constraints] = entry;
+            /** @type {Promise<rustdoc.FunctionType[]>[]} */
+            const where_clause_ = [];
+            const l = functionSearchType.length;
+            for (let i = 2; i < l; ++i) {
+                where_clause_.push(typeof functionSearchType[i] === "number"
                     // @ts-expect-error
-                    if (!this.typeNameIdMap.has(name)) {
-                        parsedQuery.error = [
-                            "Type parameter ",
-                            // @ts-expect-error
-                            name,
-                            " does not exist",
-                        ];
-                        return [0, []];
-                    }
-                    for (const elem2 of constraints) {
-                        convertNameToId(elem2, false);
-                    }
-
+                    ? Promise.all([this.buildItemSearchType(functionSearchType[i])])
                     // @ts-expect-error
-                    return [this.typeNameIdMap.get(name).id, constraints];
-                }),
-            );
+                    : this.buildItemSearchTypeAll(functionSearchType[i]),
+                );
+            }
+            const [inputs, output, where_clause] = await Promise.all([
+                inputs_,
+                output_,
+                Promise.all(where_clause_),
+            ]);
+            return {
+                inputs, output, where_clause,
+            };
+        });
+
+        return {
+            functionSignature: await parser.next(),
+            paramNames: raw[1],
+            elemCount: parser.elemCount,
         };
+    }
 
-        for (const elem of parsedQuery.elems) {
-            convertNameToId(elem, false);
-            this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint);
+    /**
+     * @param {number} id
+     * @returns {Promise<rustdoc.TypeData|null>}
+     */
+    async getTypeData(id) {
+        const ti = this.database.getData("type");
+        if (!ti) {
+            return null;
         }
-        for (const elem of parsedQuery.returned) {
-            convertNameToId(elem, false);
-            this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint);
+        const encoded = this.utf8decoder.decode(await ti.at(id));
+        if (encoded === "" || encoded === undefined || encoded === null) {
+            return null;
         }
+        /**
+         * function_signature, param_names
+         * @type {[string, number] | [number] | [string] | [] | null}
+         */
+        const raw = JSON.parse(encoded);
+
+        if (!raw || raw.length === 0) {
+            return null;
+        }
+
+        let searchUnbox = false;
+        const invertedFunctionSignatureIndex = [];
 
+        if (typeof raw[0] === "string") {
+            if (raw[1]) {
+                searchUnbox = true;
+            }
+            // the inverted function signature index is a list of bitmaps,
+            // by number of types that appear in the function
+            let i = 0;
+            const pb = makeUint8ArrayFromBase64(raw[0]);
+            const l = pb.length;
+            while (i < l) {
+                if (pb[i] === 0) {
+                    invertedFunctionSignatureIndex.push(RoaringBitmap.empty());
+                    i += 1;
+                } else {
+                    const bitmap = new RoaringBitmap(pb, i);
+                    i += bitmap.consumed_len_bytes;
+                    invertedFunctionSignatureIndex.push(bitmap);
+                }
+            }
+        } else if (raw[0]) {
+            searchUnbox = true;
+        }
 
+        return { searchUnbox, invertedFunctionSignatureIndex };
+    }
+
+    /**
+     * @returns {Promise<string[]>}
+     */
+    async getCrateNameList() {
+        const crateNames = this.database.getData("crateNames");
+        if (!crateNames) {
+            return [];
+        }
+        const l = crateNames.length;
+        const names = [];
+        for (let i = 0; i < l; ++i) {
+            names.push(crateNames.at(i).then(name => {
+                if (name === undefined) {
+                    return "";
+                }
+                return this.utf8decoder.decode(name);
+            }));
+        }
+        return Promise.all(names);
+    }
+
+    /**
+     * @param {number} id non-negative generic index
+     * @returns {Promise<stringdex.RoaringBitmap[]>}
+     */
+    async getGenericInvertedIndex(id) {
+        const gii = this.database.getData("generic_inverted_index");
+        if (!gii) {
+            return [];
+        }
+        const pb = await gii.at(id);
+        if (pb === undefined || pb === null || pb.length === 0) {
+            return [];
+        }
+
+        const invertedFunctionSignatureIndex = [];
+        // the inverted function signature index is a list of bitmaps,
+        // by number of types that appear in the function
+        let i = 0;
+        const l = pb.length;
+        while (i < l) {
+            if (pb[i] === 0) {
+                invertedFunctionSignatureIndex.push(RoaringBitmap.empty());
+                i += 1;
+            } else {
+                const bitmap = new RoaringBitmap(pb, i);
+                i += bitmap.consumed_len_bytes;
+                invertedFunctionSignatureIndex.push(bitmap);
+            }
+        }
+        return invertedFunctionSignatureIndex;
+    }
+
+    /**
+     * @param {number} id
+     * @returns {Promise<rustdoc.Row?>}
+     */
+    async getRow(id) {
+        const [name_, entry, path, type] = await Promise.all([
+            this.getName(id),
+            this.getEntryData(id),
+            this.getPathData(id),
+            this.getFunctionData(id),
+        ]);
+        if (!entry && !path) {
+            return null;
+        }
+        const [
+            moduleName,
+            modulePathData,
+            exactModuleName,
+            exactModulePathData,
+        ] = await Promise.all([
+            entry && entry.modulePath !== null ? this.getName(entry.modulePath) : null,
+            entry && entry.modulePath !== null ? this.getPathData(entry.modulePath) : null,
+            entry && entry.exactModulePath !== null ?
+                this.getName(entry.exactModulePath) :
+                null,
+            entry && entry.exactModulePath !== null ?
+                this.getPathData(entry.exactModulePath) :
+                null,
+        ]);
+        const name = name_ === null ? "" : name_;
+        const normalizedName = (name.indexOf("_") === -1 ?
+            name :
+            name.replace(/_/g, "")).toLowerCase();
+        const modulePath = modulePathData === null || moduleName === null ? "" :
+            (modulePathData.modulePath === "" ?
+                moduleName :
+                `${modulePathData.modulePath}::${moduleName}`);
+        const [parentName, parentPath] = entry !== null && entry.parent !== null ?
+            await Promise.all([this.getName(entry.parent), this.getPathData(entry.parent)]) :
+            [null, null];
+        return {
+            id,
+            crate: entry ? nonnull(await this.getName(entry.krate)) : "",
+            ty: entry ? entry.ty : nonnull(path).ty,
+            name,
+            normalizedName,
+            modulePath,
+            exactModulePath: exactModulePathData === null || exactModuleName === null ? modulePath :
+                (exactModulePathData.exactModulePath === "" ?
+                    exactModuleName :
+                    `${exactModulePathData.exactModulePath}::${exactModuleName}`),
+            entry,
+            path,
+            type,
+            deprecated: entry ? entry.deprecated : false,
+            parent: parentName !== null && parentPath !== null ?
+                { name: parentName, path: parentPath } :
+                null,
+        };
+    }
+
+    /**
+     * Convert a list of RawFunctionType / ID to object-based FunctionType.
+     *
+     * Crates often have lots of functions in them, and it's common to have a large number of
+     * functions that operate on a small set of data types, so the search index compresses them
+     * by encoding function parameter and return types as indexes into an array of names.
+     *
+     * Even when a general-purpose compression algorithm is used, this is still a win.
+     * I checked. https://github.com/rust-lang/rust/pull/98475#issue-1284395985
+     *
+     * The format for individual function types is encoded in
+     * librustdoc/html/render/mod.rs: impl Serialize for RenderType
+     *
+     * @param {null|Array<rustdoc.RawFunctionType>} types
+     *
+     * @return {Promise<Array<rustdoc.FunctionType>>}
+     */
+    async buildItemSearchTypeAll(types) {
+        return types && types.length > 0 ?
+            await Promise.all(types.map(type => this.buildItemSearchType(type))) :
+            EMPTY_GENERICS_ARRAY;
+    }
+
+    /**
+     * Converts a single type.
+     *
+     * @param {rustdoc.RawFunctionType} type
+     * @return {Promise<rustdoc.FunctionType>}
+     */
+    async buildItemSearchType(type) {
+        const PATH_INDEX_DATA = 0;
+        const GENERICS_DATA = 1;
+        const BINDINGS_DATA = 2;
+        let id, generics;
         /**
-         * Creates the query results.
-         *
-         * @param {Array<rustdoc.ResultObject>} results_in_args
-         * @param {Array<rustdoc.ResultObject>} results_returned
-         * @param {Array<rustdoc.ResultObject>} results_others
-         * @param {rustdoc.ParsedQuery<rustdoc.QueryElement>} parsedQuery
-         *
-         * @return {rustdoc.ResultsTable}
+         * @type {Map<number, rustdoc.FunctionType[]>}
          */
-        function createQueryResults(
-            results_in_args,
-            results_returned,
-            results_others,
-            parsedQuery) {
-            return {
-                "in_args": results_in_args,
-                "returned": results_returned,
-                "others": results_others,
-                "query": parsedQuery,
+        let bindings;
+        if (typeof type === "number") {
+            id = type;
+            generics = EMPTY_GENERICS_ARRAY;
+            bindings = EMPTY_BINDINGS_MAP;
+        } else {
+            id = type[PATH_INDEX_DATA];
+            generics = await this.buildItemSearchTypeAll(type[GENERICS_DATA]);
+            if (type[BINDINGS_DATA] && type[BINDINGS_DATA].length > 0) {
+                bindings = new Map((await Promise.all(type[BINDINGS_DATA].map(
+                    /**
+                     * @param {[rustdoc.RawFunctionType, rustdoc.RawFunctionType[]]} binding
+                     * @returns {Promise<[number, rustdoc.FunctionType[]][]>}
+                    */
+                    async binding => {
+                        const [assocType, constraints] = binding;
+                        // Associated type constructors are represented sloppily in rustdoc's
+                        // type search, to make the engine simpler.
+                        //
+                        // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T>
+                        // and both are, essentially
+                        // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there.
+                        // It's more like the value of a type binding is naturally an array,
+                        // which rustdoc calls "constraints".
+                        //
+                        // As a result, the key should never have generics on it.
+                        const [k, v] = await Promise.all([
+                            this.buildItemSearchType(assocType).then(t => t.id),
+                            this.buildItemSearchTypeAll(constraints),
+                        ]);
+                        return k === null ? EMPTY_BINDINGS_ARRAY : [[k, v]];
+                    },
+                ))).flat());
+            } else {
+                bindings = EMPTY_BINDINGS_MAP;
+            }
+        }
+        /**
+         * @type {rustdoc.FunctionType}
+         */
+        let result;
+        if (id < 0) {
+            // types less than 0 are generic parameters
+            // the actual names of generic parameters aren't stored, since they aren't API
+            result = {
+                id,
+                name: "",
+                ty: TY_GENERIC,
+                path: null,
+                exactPath: null,
+                generics,
+                bindings,
+                unboxFlag: true,
+            };
+        } else if (id === 0) {
+            // `0` is used as a sentinel because it's fewer bytes than `null`
+            result = {
+                id: null,
+                name: "",
+                ty: TY_GENERIC,
+                path: null,
+                exactPath: null,
+                generics,
+                bindings,
+                unboxFlag: true,
+            };
+        } else {
+            const [name, path, type] = await Promise.all([
+                this.getName(id - 1),
+                this.getPathData(id - 1),
+                this.getTypeData(id - 1),
+            ]);
+            if (path === undefined || path === null || type === undefined || type === null) {
+                return {
+                    id: null,
+                    name: "",
+                    ty: TY_GENERIC,
+                    path: null,
+                    exactPath: null,
+                    generics,
+                    bindings,
+                    unboxFlag: true,
+                };
+            }
+            result = {
+                id: id - 1,
+                name,
+                ty: path.ty,
+                path: path.modulePath,
+                exactPath: path.exactModulePath === null ? path.modulePath : path.exactModulePath,
+                generics,
+                bindings,
+                unboxFlag: type.searchUnbox,
             };
         }
+        const cr = this.TYPES_POOL.get(result.id);
+        if (cr) {
+            // Shallow equality check. Since this function is used
+            // to construct every type object, this should be mostly
+            // equivalent to a deep equality check, except if there's
+            // a conflict, we don't keep the old one around, so it's
+            // not a fully precise implementation of hashcons.
+            if (cr.generics.length === result.generics.length &&
+                cr.generics !== result.generics &&
+                cr.generics.every((x, i) => result.generics[i] === x)
+            ) {
+                result.generics = cr.generics;
+            }
+            if (cr.bindings.size === result.bindings.size && cr.bindings !== result.bindings) {
+                let ok = true;
+                for (const [k, v] of cr.bindings.entries()) {
+                    const v2 = result.bindings.get(k);
+                    if (!v2) {
+                        ok = false;
+                        break;
+                    }
+                    if (v !== v2 && v.length === v2.length && v.every((x, i) => v2[i] === x)) {
+                        result.bindings.set(k, v);
+                    } else if (v !== v2) {
+                        ok = false;
+                        break;
+                    }
+                }
+                if (ok) {
+                    result.bindings = cr.bindings;
+                }
+            }
+            if (cr.ty === result.ty && cr.path === result.path
+                && cr.bindings === result.bindings && cr.generics === result.generics
+                && cr.ty === result.ty && cr.name === result.name
+                && cr.unboxFlag === result.unboxFlag
+            ) {
+                return cr;
+            }
+        }
+        this.TYPES_POOL.set(result.id, result);
+        return result;
+    }
 
-        // @ts-expect-error
+    /**
+     * Executes the parsed query and builds a {ResultsTable}.
+     *
+     * @param  {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} parsedQuery
+     *     - The parsed user query
+     * @param  {Object} filterCrates - Crate to search in if defined
+     * @param  {string} currentCrate - Current crate, to rank results from this crate higher
+     *
+     * @return {Promise<rustdoc.ResultsTable>}
+     */
+    async execQuery(parsedQuery, filterCrates, currentCrate) {
+        const queryLen =
+            parsedQuery.elems.reduce((acc, next) => acc + next.pathLast.length, 0) +
+            parsedQuery.returned.reduce((acc, next) => acc + next.pathLast.length, 0);
+        const maxEditDistance = Math.floor(queryLen / 3);
+
+        /**
+         * @param {rustdoc.Row} item
+         * @returns {[string, string, string]}
+         */
         const buildHrefAndPath = item => {
             let displayPath;
             let href;
-            if (item.is_alias) {
-                this.FOUND_ALIASES.add(item.word);
-                item = item.original;
-            }
             const type = itemTypes[item.ty];
             const name = item.name;
-            let path = item.path;
-            let exactPath = item.exactPath;
+            let path = item.modulePath;
+            let exactPath = item.exactModulePath;
 
             if (type === "mod") {
                 displayPath = path + "::";
                 href = this.rootPath + path.replace(/::/g, "/") + "/" +
                     name + "/index.html";
             } else if (type === "import") {
-                displayPath = item.path + "::";
-                href = this.rootPath + item.path.replace(/::/g, "/") +
+                displayPath = item.modulePath + "::";
+                href = this.rootPath + item.modulePath.replace(/::/g, "/") +
                     "/index.html#reexport." + name;
             } else if (type === "primitive" || type === "keyword") {
                 displayPath = "";
@@ -2754,13 +2066,13 @@ class DocSearch {
             } else if (type === "externcrate") {
                 displayPath = "";
                 href = this.rootPath + name + "/index.html";
-            } else if (item.parent !== undefined) {
+            } else if (item.parent) {
                 const myparent = item.parent;
                 let anchor = type + "." + name;
-                const parentType = itemTypes[myparent.ty];
+                const parentType = itemTypes[myparent.path.ty];
                 let pageType = parentType;
                 let pageName = myparent.name;
-                exactPath = `${myparent.exactPath}::${myparent.name}`;
+                exactPath = `${myparent.path.exactModulePath}::${myparent.name}`;
 
                 if (parentType === "primitive") {
                     displayPath = myparent.name + "::";
@@ -2768,9 +2080,9 @@ class DocSearch {
                 } else if (type === "structfield" && parentType === "variant") {
                     // Structfields belonging to variants are special: the
                     // final path element is the enum name.
-                    const enumNameIdx = item.path.lastIndexOf("::");
-                    const enumName = item.path.substr(enumNameIdx + 2);
-                    path = item.path.substr(0, enumNameIdx);
+                    const enumNameIdx = item.modulePath.lastIndexOf("::");
+                    const enumName = item.modulePath.substr(enumNameIdx + 2);
+                    path = item.modulePath.substr(0, enumNameIdx);
                     displayPath = path + "::" + enumName + "::" + myparent.name + "::";
                     anchor = "variant." + myparent.name + ".field." + name;
                     pageType = "enum";
@@ -2778,16 +2090,16 @@ class DocSearch {
                 } else {
                     displayPath = path + "::" + myparent.name + "::";
                 }
-                if (item.implDisambiguator !== null) {
-                    anchor = item.implDisambiguator + "/" + anchor;
+                if (item.entry && item.entry.associatedItemDisambiguator !== null) {
+                    anchor = item.entry.associatedItemDisambiguator + "/" + anchor;
                 }
                 href = this.rootPath + path.replace(/::/g, "/") +
                     "/" + pageType +
                     "." + pageName +
                     ".html#" + anchor;
             } else {
-                displayPath = item.path + "::";
-                href = this.rootPath + item.path.replace(/::/g, "/") +
+                displayPath = item.modulePath + "::";
+                href = this.rootPath + item.modulePath.replace(/::/g, "/") +
                     "/" + type + "." + name + ".html";
             }
             return [displayPath, href, `${exactPath}::${name}`];
@@ -2810,82 +2122,16 @@ class DocSearch {
          * Add extra data to result objects, and filter items that have been
          * marked for removal.
          *
-         * @param {rustdoc.ResultObject[]} results
-         * @param {"sig"|"elems"|"returned"|null} typeInfo
-         * @returns {rustdoc.ResultObject[]}
-         */
-        const transformResults = (results, typeInfo) => {
-            const duplicates = new Set();
-            const out = [];
-
-            for (const result of results) {
-                if (result.id !== -1) {
-                    const res = buildHrefAndPath(this.searchIndex[result.id]);
-                    // many of these properties don't strictly need to be
-                    // copied over, but copying them over satisfies tsc,
-                    // and hopefully plays nice with the shape optimization
-                    // of the browser engine.
-                    /** @type {rustdoc.ResultObject} */
-                    const obj = Object.assign({
-                        parent: result.parent,
-                        type: result.type,
-                        dist: result.dist,
-                        path_dist: result.path_dist,
-                        index: result.index,
-                        desc: result.desc,
-                        item: result.item,
-                        displayPath: pathSplitter(res[0]),
-                        fullPath: "",
-                        href: "",
-                        displayTypeSignature: null,
-                    }, this.searchIndex[result.id]);
-
-                    // To be sure than it some items aren't considered as duplicate.
-                    obj.fullPath = res[2] + "|" + obj.ty;
-
-                    if (duplicates.has(obj.fullPath)) {
-                        continue;
-                    }
-
-                    // Exports are specifically not shown if the items they point at
-                    // are already in the results.
-                    if (obj.ty === TY_IMPORT && duplicates.has(res[2])) {
-                        continue;
-                    }
-                    if (duplicates.has(res[2] + "|" + TY_IMPORT)) {
-                        continue;
-                    }
-                    duplicates.add(obj.fullPath);
-                    duplicates.add(res[2]);
-
-                    if (typeInfo !== null) {
-                        obj.displayTypeSignature =
-                            // @ts-expect-error
-                            this.formatDisplayTypeSignature(obj, typeInfo);
-                    }
-
-                    obj.href = res[1];
-                    out.push(obj);
-                    if (out.length >= MAX_RESULTS) {
-                        break;
-                    }
-                }
-            }
-            return out;
-        };
-
-        /**
-         * Add extra data to result objects, and filter items that have been
-         * marked for removal.
-         *
          * The output is formatted as an array of hunks, where odd numbered
          * hunks are highlighted and even numbered ones are not.
          *
          * @param {rustdoc.ResultObject} obj
          * @param {"sig"|"elems"|"returned"|null} typeInfo
+         * @param {rustdoc.QueryElement[]} elems
+         * @param {rustdoc.QueryElement[]} returned
          * @returns {Promise<rustdoc.DisplayTypeSignature>}
          */
-        this.formatDisplayTypeSignature = async(obj, typeInfo) => {
+        const formatDisplayTypeSignature = async(obj, typeInfo, elems, returned) => {
             const objType = obj.type;
             if (!objType) {
                 return {type: [], mappedNames: new Map(), whereClause: new Map()};
@@ -2897,13 +2143,13 @@ class DocSearch {
             if (typeInfo !== "elems" && typeInfo !== "returned") {
                 fnInputs = unifyFunctionTypes(
                     objType.inputs,
-                    parsedQuery.elems,
+                    elems,
                     objType.where_clause,
                     null,
                     mgensScratch => {
                         fnOutput = unifyFunctionTypes(
                             objType.output,
-                            parsedQuery.returned,
+                            returned,
                             objType.where_clause,
                             mgensScratch,
                             mgensOut => {
@@ -2917,10 +2163,9 @@ class DocSearch {
                     0,
                 );
             } else {
-                const arr = typeInfo === "elems" ? objType.inputs : objType.output;
                 const highlighted = unifyFunctionTypes(
-                    arr,
-                    parsedQuery.elems,
+                    typeInfo === "elems" ? objType.inputs : objType.output,
+                    typeInfo === "elems" ? elems : returned,
                     objType.where_clause,
                     null,
                     mgensOut => {
@@ -2969,15 +2214,15 @@ class DocSearch {
                 }
             };
 
-            parsedQuery.elems.forEach(remapQuery);
-            parsedQuery.returned.forEach(remapQuery);
+            elems.forEach(remapQuery);
+            returned.forEach(remapQuery);
 
             /**
              * Write text to a highlighting array.
              * Index 0 is not highlighted, index 1 is highlighted,
              * index 2 is not highlighted, etc.
              *
-             * @param {{name?: string, highlighted?: boolean}} fnType - input
+             * @param {{name: string|null, highlighted?: boolean}} fnType - input
              * @param {string[]} result
              */
             const pushText = (fnType, result) => {
@@ -3004,8 +2249,9 @@ class DocSearch {
              *
              * @param {rustdoc.HighlightedFunctionType} fnType - input
              * @param {string[]} result
+             * @returns {Promise<void>}
              */
-            const writeHof = (fnType, result) => {
+            const writeHof = async(fnType, result) => {
                 const hofOutput = fnType.bindings.get(this.typeNameIdOfOutput) || [];
                 const hofInputs = fnType.generics;
                 pushText(fnType, result);
@@ -3016,7 +2262,7 @@ class DocSearch {
                         pushText({ name: ", ", highlighted: false }, result);
                     }
                     needsComma = true;
-                    writeFn(fnType, result);
+                    await writeFn(fnType, result);
                 }
                 pushText({
                     name: hofOutput.length === 0 ? ")" : ") -> ",
@@ -3031,7 +2277,7 @@ class DocSearch {
                         pushText({ name: ", ", highlighted: false }, result);
                     }
                     needsComma = true;
-                    writeFn(fnType, result);
+                    await writeFn(fnType, result);
                 }
                 if (hofOutput.length > 1) {
                     pushText({name: ")", highlighted: false}, result);
@@ -3044,8 +2290,9 @@ class DocSearch {
              *
              * @param {rustdoc.HighlightedFunctionType} fnType
              * @param {string[]} result
+             * @returns {Promise<boolean>}
              */
-            const writeSpecialPrimitive = (fnType, result) => {
+            const writeSpecialPrimitive = async(fnType, result) => {
                 if (fnType.id === this.typeNameIdOfArray || fnType.id === this.typeNameIdOfSlice ||
                     fnType.id === this.typeNameIdOfTuple || fnType.id === this.typeNameIdOfUnit) {
                     const [ob, sb] =
@@ -3054,7 +2301,7 @@ class DocSearch {
                         ["[", "]"] :
                         ["(", ")"];
                     pushText({ name: ob, highlighted: fnType.highlighted }, result);
-                    onEachBtwn(
+                    await onEachBtwnAsync(
                         fnType.generics,
                         nested => writeFn(nested, result),
                         // @ts-expect-error
@@ -3065,11 +2312,11 @@ class DocSearch {
                 } else if (fnType.id === this.typeNameIdOfReference) {
                     pushText({ name: "&", highlighted: fnType.highlighted }, result);
                     let prevHighlighted = false;
-                    onEachBtwn(
+                    await onEachBtwnAsync(
                         fnType.generics,
-                        value => {
+                        async value => {
                             prevHighlighted = !!value.highlighted;
-                            writeFn(value, result);
+                            await writeFn(value, result);
                         },
                         // @ts-expect-error
                         value => pushText({
@@ -3078,8 +2325,35 @@ class DocSearch {
                         }, result),
                     );
                     return true;
-                } else if (fnType.id === this.typeNameIdOfFn) {
-                    writeHof(fnType, result);
+                } else if (fnType.id === this.typeNameIdOfPointer) {
+                    pushText({ name: "*", highlighted: fnType.highlighted }, result);
+                    if (fnType.generics.length < 2) {
+                        pushText({ name: "const ", highlighted: fnType.highlighted }, result);
+                    }
+                    let prevHighlighted = false;
+                    await onEachBtwnAsync(
+                        fnType.generics,
+                        async value => {
+                            prevHighlighted = !!value.highlighted;
+                            await writeFn(value, result);
+                        },
+                        // @ts-expect-error
+                        value => pushText({
+                            name: " ",
+                            highlighted: prevHighlighted && value.highlighted,
+                        }, result),
+                    );
+                    return true;
+                } else if (
+                    fnType.id === this.typeNameIdOfFn ||
+                    fnType.id === this.typeNameIdOfFnMut ||
+                    fnType.id === this.typeNameIdOfFnOnce ||
+                    fnType.id === this.typeNameIdOfFnPtr
+                ) {
+                    await writeHof(fnType, result);
+                    return true;
+                } else if (fnType.id === this.typeNameIdOfNever) {
+                    pushText({ name: "!", highlighted: fnType.highlighted }, result);
                     return true;
                 }
                 return false;
@@ -3091,8 +2365,9 @@ class DocSearch {
              *
              * @param {rustdoc.HighlightedFunctionType} fnType
              * @param {string[]} result
+             * @returns {Promise<void>}
              */
-            const writeFn = (fnType, result) => {
+            const writeFn = async(fnType, result) => {
                 if (fnType.id !== null && fnType.id < 0) {
                     if (fnParamNames[-1 - fnType.id] === "") {
                         // Normally, there's no need to shown an unhighlighted
@@ -3101,7 +2376,7 @@ class DocSearch {
                             fnType.generics :
                             objType.where_clause[-1 - fnType.id];
                         for (const nested of generics) {
-                            writeFn(nested, result);
+                            await writeFn(nested, result);
                         }
                         return;
                     } else if (mgens) {
@@ -3120,7 +2395,7 @@ class DocSearch {
                     }, result);
                     /** @type{string[]} */
                     const where = [];
-                    onEachBtwn(
+                    await onEachBtwnAsync(
                         fnType.generics,
                         nested => writeFn(nested, where),
                         // @ts-expect-error
@@ -3131,32 +2406,61 @@ class DocSearch {
                     }
                 } else {
                     if (fnType.ty === TY_PRIMITIVE) {
-                        if (writeSpecialPrimitive(fnType, result)) {
+                        if (await writeSpecialPrimitive(fnType, result)) {
                             return;
                         }
                     } else if (fnType.ty === TY_TRAIT && (
                         fnType.id === this.typeNameIdOfFn ||
-                            fnType.id === this.typeNameIdOfFnMut ||
-                            fnType.id === this.typeNameIdOfFnOnce)) {
-                        writeHof(fnType, result);
+                        fnType.id === this.typeNameIdOfFnMut ||
+                        fnType.id === this.typeNameIdOfFnOnce ||
+                        fnType.id === this.typeNameIdOfFnPtr
+                    )) {
+                        await writeHof(fnType, result);
+                        return;
+                    } else if (fnType.name === "" &&
+                        fnType.bindings.size === 0 &&
+                        fnType.generics.length !== 0
+                    ) {
+                        pushText({ name: "impl ", highlighted: false }, result);
+                        if (fnType.generics.length > 1) {
+                            pushText({ name: "(", highlighted: false }, result);
+                        }
+                        await onEachBtwnAsync(
+                            fnType.generics,
+                            value => writeFn(value, result),
+                            // @ts-expect-error
+                            () => pushText({ name: ", ",  highlighted: false }, result),
+                        );
+                        if (fnType.generics.length > 1) {
+                            pushText({ name: ")", highlighted: false }, result);
+                        }
                         return;
                     }
                     pushText(fnType, result);
                     let hasBindings = false;
                     if (fnType.bindings.size > 0) {
-                        onEachBtwn(
-                            fnType.bindings,
-                            ([key, values]) => {
-                                const name = this.assocTypeIdNameMap.get(key);
+                        await onEachBtwnAsync(
+                            await Promise.all([...fnType.bindings.entries()].map(
+                                /**
+                                 * @param {[number, rustdoc.HighlightedFunctionType[]]} param0
+                                 * @returns {Promise<[
+                                 *     string|null,
+                                 *     rustdoc.HighlightedFunctionType[],
+                                 * ]>}
+                                 */
+                                async([key, values]) => [await this.getName(key), values],
+                            )),
+                            async([name, values]) => {
                                 // @ts-expect-error
                                 if (values.length === 1 && values[0].id < 0 &&
                                     // @ts-expect-error
-                                    `${fnType.name}::${name}` === fnParamNames[-1 - values[0].id]) {
+                                    `${fnType.name}::${name}` === fnParamNames[-1 - values[0].id]
+                                ) {
                                     // the internal `Item=Iterator::Item` type variable should be
                                     // shown in the where clause and name mapping output, but is
                                     // redundant in this spot
                                     for (const value of values) {
-                                        writeFn(value, []);
+                                        await writeFn(value, []);
                                     }
                                     return true;
                                 }
@@ -3169,7 +2473,7 @@ class DocSearch {
                                     name: values.length !== 1 ? "=(" : "=",
                                     highlighted: false,
                                 }, result);
-                                onEachBtwn(
+                                await onEachBtwnAsync(
                                     values || [],
                                     value => writeFn(value, result),
                                     // @ts-expect-error
@@ -3186,7 +2490,7 @@ class DocSearch {
                     if (fnType.generics.length > 0) {
                         pushText({ name: hasBindings ? ", " : "<", highlighted: false }, result);
                     }
-                    onEachBtwn(
+                    await onEachBtwnAsync(
                         fnType.generics,
                         value => writeFn(value, result),
                         // @ts-expect-error
@@ -3199,14 +2503,14 @@ class DocSearch {
             };
             /** @type {string[]} */
             const type = [];
-            onEachBtwn(
+            await onEachBtwnAsync(
                 fnInputs,
                 fnType => writeFn(fnType, type),
                 // @ts-expect-error
                 () => pushText({ name: ", ",  highlighted: false }, type),
             );
             pushText({ name: " -> ", highlighted: false }, type);
-            onEachBtwn(
+            await onEachBtwnAsync(
                 fnOutput,
                 fnType => writeFn(fnType, type),
                 // @ts-expect-error
@@ -3217,176 +2521,252 @@ class DocSearch {
         };
 
         /**
-         * This function takes a result map, and sorts it by various criteria, including edit
-         * distance, substring match, and the crate it comes from.
+         * Add extra data to result objects, and filter items that have been
+         * marked for removal.
          *
-         * @param {rustdoc.Results} results
+         * @param {[rustdoc.PlainResultObject, rustdoc.Row][]} results
          * @param {"sig"|"elems"|"returned"|null} typeInfo
-         * @param {string} preferredCrate
-         * @returns {Promise<rustdoc.ResultObject[]>}
+         * @param {Set<string>} duplicates
+         * @returns {rustdoc.ResultObject[]}
          */
-        const sortResults = async(results, typeInfo, preferredCrate) => {
-            const userQuery = parsedQuery.userQuery;
-            const normalizedUserQuery = parsedQuery.userQuery.toLowerCase();
-            const isMixedCase = normalizedUserQuery !== userQuery;
-            const result_list = [];
-            const isReturnTypeQuery = parsedQuery.elems.length === 0 ||
-                typeInfo === "returned";
-            for (const result of results.values()) {
-                result.item = this.searchIndex[result.id];
-                result.word = this.searchIndex[result.id].word;
-                if (isReturnTypeQuery) {
-                    // We are doing a return-type based search, deprioritize "clone-like" results,
-                    // ie. functions that also take the queried type as an argument.
-                    const resultItemType = result.item && result.item.type;
-                    if (!resultItemType) {
+        const transformResults = (results, typeInfo, duplicates) => {
+            const out = [];
+
+            for (const [result, item] of results) {
+                if (item.id !== -1) {
+                    const res = buildHrefAndPath(item);
+                    // many of these properties don't strictly need to be
+                    // copied over, but copying them over satisfies tsc,
+                    // and hopefully plays nice with the shape optimization
+                    // of the browser engine.
+                    /** @type {rustdoc.ResultObject} */
+                    const obj = Object.assign({
+                        parent: item.parent ? {
+                            path: item.parent.path.modulePath,
+                            exactPath: item.parent.path.exactModulePath ||
+                                item.parent.path.modulePath,
+                            name: item.parent.name,
+                            ty: item.parent.path.ty,
+                        } : undefined,
+                        type: item.type && item.type.functionSignature ?
+                            item.type.functionSignature :
+                            undefined,
+                        paramNames: item.type && item.type.paramNames ?
+                            item.type.paramNames :
+                            undefined,
+                        dist: result.dist,
+                        path_dist: result.path_dist,
+                        index: result.index,
+                        desc: this.getDesc(result.id),
+                        item,
+                        displayPath: pathSplitter(res[0]),
+                        fullPath: "",
+                        href: "",
+                        displayTypeSignature: null,
+                    }, result);
+
+                    // To be sure than it some items aren't considered as duplicate.
+                    obj.fullPath = res[2] + "|" + obj.item.ty;
+
+                    if (duplicates.has(obj.fullPath)) {
+                        continue;
+                    }
+
+                    // Exports are specifically not shown if the items they point at
+                    // are already in the results.
+                    if (obj.item.ty === TY_IMPORT && duplicates.has(res[2])) {
                         continue;
                     }
-                    const inputs = resultItemType.inputs;
-                    const where_clause = resultItemType.where_clause;
-                    if (containsTypeFromQuery(inputs, where_clause)) {
-                        result.path_dist *= 100;
-                        result.dist *= 100;
+                    if (duplicates.has(res[2] + "|" + TY_IMPORT)) {
+                        continue;
+                    }
+                    duplicates.add(obj.fullPath);
+                    duplicates.add(res[2]);
+
+                    if (typeInfo !== null) {
+                        obj.displayTypeSignature = formatDisplayTypeSignature(
+                            obj,
+                            typeInfo,
+                            result.elems,
+                            result.returned,
+                        );
+                    }
+
+                    obj.href = res[1];
+                    out.push(obj);
+                    if (out.length >= MAX_RESULTS) {
+                        break;
                     }
                 }
-                result_list.push(result);
             }
 
-            result_list.sort((aaa, bbb) => {
-                /** @type {number} */
-                let a;
-                /** @type {number} */
-                let b;
+            return out;
+        };
 
-                // sort by exact case-sensitive match
-                if (isMixedCase) {
-                    a = Number(aaa.item.name !== userQuery);
-                    b = Number(bbb.item.name !== userQuery);
-                    if (a !== b) {
-                        return a - b;
+        const sortAndTransformResults =
+            /**
+             * @this {DocSearch}
+             * @param {Array<rustdoc.PlainResultObject|null>} results
+             * @param {"sig"|"elems"|"returned"|null} typeInfo
+             * @param {string} preferredCrate
+             * @param {Set<string>} duplicates
+             * @returns {AsyncGenerator<rustdoc.ResultObject, number>}
+             */
+            async function*(results, typeInfo, preferredCrate, duplicates) {
+                const userQuery = parsedQuery.userQuery;
+                const normalizedUserQuery = parsedQuery.userQuery.toLowerCase();
+                const isMixedCase = normalizedUserQuery !== userQuery;
+                /**
+                 * @type {[rustdoc.PlainResultObject, rustdoc.Row][]}
+                 */
+                const result_list = [];
+                for (const result of results.values()) {
+                    if (!result) {
+                        continue;
+                    }
+                    /**
+                     * @type {rustdoc.Row?}
+                     */
+                    const item = await this.getRow(result.id);
+                    if (!item) {
+                        continue;
+                    }
+                    if (filterCrates !== null && item.crate !== filterCrates) {
+                        continue;
+                    }
+                    if (item) {
+                        result_list.push([result, item]);
+                    } else {
+                        continue;
                     }
                 }
 
-                // sort by exact match with regard to the last word (mismatch goes later)
-                a = Number(aaa.word !== normalizedUserQuery);
-                b = Number(bbb.word !== normalizedUserQuery);
-                if (a !== b) {
-                    return a - b;
-                }
+                result_list.sort(([aaa, aai], [bbb, bbi]) => {
+                    /** @type {number} */
+                    let a;
+                    /** @type {number} */
+                    let b;
+
+                    if (typeInfo === null) {
+                        // in name based search...
+
+                        // sort by exact case-sensitive match
+                        if (isMixedCase) {
+                            a = Number(aai.name !== userQuery);
+                            b = Number(bbi.name !== userQuery);
+                            if (a !== b) {
+                                return a - b;
+                            }
+                        }
 
-                // sort by index of keyword in item name (no literal occurrence goes later)
-                a = Number(aaa.index < 0);
-                b = Number(bbb.index < 0);
-                if (a !== b) {
-                    return a - b;
-                }
+                        // sort by exact match with regard to the last word (mismatch goes later)
+                        a = Number(aai.normalizedName !== normalizedUserQuery);
+                        b = Number(bbi.normalizedName !== normalizedUserQuery);
+                        if (a !== b) {
+                            return a - b;
+                        }
+
+                        // sort by index of keyword in item name (no literal occurrence goes later)
+                        a = Number(aaa.index < 0);
+                        b = Number(bbb.index < 0);
+                        if (a !== b) {
+                            return a - b;
+                        }
+                    }
 
-                // in type based search, put functions first
-                if (parsedQuery.hasReturnArrow) {
-                    a = Number(!isFnLikeTy(aaa.item.ty));
-                    b = Number(!isFnLikeTy(bbb.item.ty));
+                    // Sort by distance in the path part, if specified
+                    // (less changes required to match means higher rankings)
+                    a = Number(aaa.path_dist);
+                    b = Number(bbb.path_dist);
                     if (a !== b) {
                         return a - b;
                     }
-                }
 
-                // Sort by distance in the path part, if specified
-                // (less changes required to match means higher rankings)
-                a = Number(aaa.path_dist);
-                b = Number(bbb.path_dist);
-                if (a !== b) {
-                    return a - b;
-                }
-
-                // (later literal occurrence, if any, goes later)
-                a = Number(aaa.index);
-                b = Number(bbb.index);
-                if (a !== b) {
-                    return a - b;
-                }
+                    // (later literal occurrence, if any, goes later)
+                    a = Number(aaa.index);
+                    b = Number(bbb.index);
+                    if (a !== b) {
+                        return a - b;
+                    }
 
-                // Sort by distance in the name part, the last part of the path
-                // (less changes required to match means higher rankings)
-                a = Number(aaa.dist);
-                b = Number(bbb.dist);
-                if (a !== b) {
-                    return a - b;
-                }
+                    // Sort by distance in the name part, the last part of the path
+                    // (less changes required to match means higher rankings)
+                    a = Number(aaa.dist);
+                    b = Number(bbb.dist);
+                    if (a !== b) {
+                        return a - b;
+                    }
 
-                // sort deprecated items later
-                a = Number(
-                    // @ts-expect-error
-                    this.searchIndexDeprecated.get(aaa.item.crate).contains(aaa.item.bitIndex),
-                );
-                b = Number(
-                    // @ts-expect-error
-                    this.searchIndexDeprecated.get(bbb.item.crate).contains(bbb.item.bitIndex),
-                );
-                if (a !== b) {
-                    return a - b;
-                }
+                    // sort aliases lower
+                    a = Number(aaa.is_alias);
+                    b = Number(bbb.is_alias);
+                    if (a !== b) {
+                        return a - b;
+                    }
 
-                // sort by crate (current crate comes first)
-                a = Number(aaa.item.crate !== preferredCrate);
-                b = Number(bbb.item.crate !== preferredCrate);
-                if (a !== b) {
-                    return a - b;
-                }
+                    // sort deprecated items later
+                    a = Number(aai.deprecated);
+                    b = Number(bbi.deprecated);
+                    if (a !== b) {
+                        return a - b;
+                    }
 
-                // sort by item name length (longer goes later)
-                a = Number(aaa.word.length);
-                b = Number(bbb.word.length);
-                if (a !== b) {
-                    return a - b;
-                }
+                    // sort by crate (current crate comes first)
+                    a = Number(aai.crate !== preferredCrate);
+                    b = Number(bbi.crate !== preferredCrate);
+                    if (a !== b) {
+                        return a - b;
+                    }
 
-                // sort doc alias items later
-                a = Number(aaa.item.is_alias === true);
-                b = Number(bbb.item.is_alias === true);
-                if (a !== b) {
-                    return a - b;
-                }
+                    // sort by item name length (longer goes later)
+                    a = Number(aai.normalizedName.length);
+                    b = Number(bbi.normalizedName.length);
+                    if (a !== b) {
+                        return a - b;
+                    }
 
-                // sort by item name (lexicographically larger goes later)
-                let aw = aaa.word;
-                let bw = bbb.word;
-                if (aw !== bw) {
-                    return (aw > bw ? +1 : -1);
-                }
+                    // sort by item name (lexicographically larger goes later)
+                    let aw = aai.normalizedName;
+                    let bw = bbi.normalizedName;
+                    if (aw !== bw) {
+                        return (aw > bw ? +1 : -1);
+                    }
 
-                // sort by description (no description goes later)
-                a = Number(
-                    // @ts-expect-error
-                    this.searchIndexEmptyDesc.get(aaa.item.crate).contains(aaa.item.bitIndex),
-                );
-                b = Number(
-                    // @ts-expect-error
-                    this.searchIndexEmptyDesc.get(bbb.item.crate).contains(bbb.item.bitIndex),
-                );
-                if (a !== b) {
-                    return a - b;
-                }
+                    // sort by description (no description goes later)
+                    const di = this.database.getData("desc");
+                    if (di) {
+                        a = Number(di.isEmpty(aaa.id));
+                        b = Number(di.isEmpty(bbb.id));
+                        if (a !== b) {
+                            return a - b;
+                        }
+                    }
 
-                // sort by type (later occurrence in `itemTypes` goes later)
-                a = Number(aaa.item.ty);
-                b = Number(bbb.item.ty);
-                if (a !== b) {
-                    return a - b;
-                }
+                    // sort by type (later occurrence in `itemTypes` goes later)
+                    a = Number(aai.ty);
+                    b = Number(bbi.ty);
+                    if (a !== b) {
+                        return a - b;
+                    }
 
-                // sort by path (lexicographically larger goes later)
-                aw = aaa.item.path;
-                bw = bbb.item.path;
-                if (aw !== bw) {
-                    return (aw > bw ? +1 : -1);
-                }
+                    // sort by path (lexicographically larger goes later)
+                    const ap = aai.modulePath;
+                    const bp = bbi.modulePath;
+                    aw = ap === undefined ? "" : ap;
+                    bw = bp === undefined ? "" : bp;
+                    if (aw !== bw) {
+                        return (aw > bw ? +1 : -1);
+                    }
 
-                // que sera, sera
-                return 0;
-            });
+                    // que sera, sera
+                    return 0;
+                });
 
-            return transformResults(result_list, typeInfo);
-        };
+                const transformed_result_list = transformResults(result_list, typeInfo, duplicates);
+                yield* transformed_result_list;
+                return transformed_result_list.length;
+            }
+            .bind(this);
 
         /**
          * This function checks if a list of search query `queryElems` can all be found in the
@@ -3938,6 +3318,8 @@ class DocSearch {
                 }
                 return true;
             } else {
+                // For these special cases, matching code need added to the inverted index.
+                // search_index.rs -> convert_render_type does this
                 if (queryElem.id === this.typeNameIdOfArrayOrSlice &&
                     (fnType.id === this.typeNameIdOfSlice || fnType.id === this.typeNameIdOfArray)
                 ) {
@@ -3948,10 +3330,12 @@ class DocSearch {
                 ) {
                     // () matches primitive:tuple or primitive:unit
                     // if it matches, then we're fine, and this is an appropriate match candidate
-                } else if (queryElem.id === this.typeNameIdOfHof &&
-                    (fnType.id === this.typeNameIdOfFn || fnType.id === this.typeNameIdOfFnMut ||
-                        fnType.id === this.typeNameIdOfFnOnce)
-                ) {
+                } else if (queryElem.id === this.typeNameIdOfHof && (
+                    fnType.id === this.typeNameIdOfFn ||
+                    fnType.id === this.typeNameIdOfFnMut ||
+                    fnType.id === this.typeNameIdOfFnOnce ||
+                    fnType.id === this.typeNameIdOfFnPtr
+                )) {
                     // -> matches fn, fnonce, and fnmut
                     // if it matches, then we're fine, and this is an appropriate match candidate
                 } else if (fnType.id !== queryElem.id || queryElem.id === null) {
@@ -4134,21 +3518,13 @@ class DocSearch {
          * This function checks if the given list contains any
          * (non-generic) types mentioned in the query.
          *
+         * @param {rustdoc.QueryElement[]} elems
          * @param {rustdoc.FunctionType[]} list    - A list of function types.
          * @param {rustdoc.FunctionType[][]} where_clause - Trait bounds for generic items.
          */
-        function containsTypeFromQuery(list, where_clause) {
+        function containsTypeFromQuery(elems, list, where_clause) {
             if (!list) return false;
-            for (const ty of parsedQuery.returned) {
-                // negative type ids are generics
-                if (ty.id !== null && ty.id < 0) {
-                    continue;
-                }
-                if (checkIfInList(list, ty, where_clause, null, 0)) {
-                    return true;
-                }
-            }
-            for (const ty of parsedQuery.elems) {
+            for (const ty of elems) {
                 if (ty.id !== null && ty.id < 0) {
                     continue;
                 }
@@ -4240,10 +3616,10 @@ class DocSearch {
         /**
          * Compute an "edit distance" that ignores missing path elements.
          * @param {string[]} contains search query path
-         * @param {rustdoc.Row} ty indexed item
+         * @param {string[]} path indexed page path
          * @returns {null|number} edit distance
          */
-        function checkPath(contains, ty) {
+        function checkPath(contains, path) {
             if (contains.length === 0) {
                 return 0;
             }
@@ -4251,11 +3627,6 @@ class DocSearch {
                 contains.reduce((acc, next) => acc + next.length, 0) / 3,
             );
             let ret_dist = maxPathEditDistance + 1;
-            const path = ty.path.split("::");
-
-            if (ty.parent && ty.parent.name) {
-                path.push(ty.parent.name.toLowerCase());
-            }
 
             const length = path.length;
             const clength = contains.length;
@@ -4281,7 +3652,32 @@ class DocSearch {
             return ret_dist > maxPathEditDistance ? null : ret_dist;
         }
 
-        // @ts-expect-error
+        /**
+         * Compute an "edit distance" that ignores missing path elements.
+         * @param {string[]} contains search query path
+         * @param {rustdoc.Row} row indexed item
+         * @returns {null|number} edit distance
+         */
+        function checkRowPath(contains, row) {
+            if (contains.length === 0) {
+                return 0;
+            }
+
+            const path = row.modulePath.split("::");
+
+            if (row.parent && row.parent.name) {
+                path.push(row.parent.name.toLowerCase());
+            }
+
+            return checkPath(contains, path);
+        }
+
+        /**
+         *
+         * @param {number} filter
+         * @param {rustdoc.ItemType} type
+         * @returns
+         */
         function typePassesFilter(filter, type) {
             // No filter or Exact mach
             if (filter <= NO_TYPE_FILTER || filter === type) return true;
@@ -4303,366 +3699,839 @@ class DocSearch {
             return false;
         }
 
-        // @ts-expect-error
-        const handleAliases = async(ret, query, filterCrates, currentCrate) => {
-            const lowerQuery = query.toLowerCase();
-            if (this.FOUND_ALIASES.has(lowerQuery)) {
-                return;
-            }
-            this.FOUND_ALIASES.add(lowerQuery);
-            // We separate aliases and crate aliases because we want to have current crate
-            // aliases to be before the others in the displayed results.
-            // @ts-expect-error
-            const aliases = [];
-            // @ts-expect-error
-            const crateAliases = [];
-            if (filterCrates !== null) {
-                if (this.ALIASES.has(filterCrates)
-                    && this.ALIASES.get(filterCrates).has(lowerQuery)) {
-                    const query_aliases = this.ALIASES.get(filterCrates).get(lowerQuery);
-                    for (const alias of query_aliases) {
-                        aliases.push(alias);
-                    }
+        const innerRunNameQuery =
+            /**
+             * @this {DocSearch}
+             * @param {string} currentCrate
+             * @returns {AsyncGenerator<rustdoc.ResultObject>}
+             */
+            async function*(currentCrate) {
+                const index = this.database.getIndex("normalizedName");
+                if (!index) {
+                    return;
                 }
-            } else {
-                for (const [crate, crateAliasesIndex] of this.ALIASES) {
-                    if (crateAliasesIndex.has(lowerQuery)) {
-                        // @ts-expect-error
-                        const pushTo = crate === currentCrate ? crateAliases : aliases;
-                        const query_aliases = crateAliasesIndex.get(lowerQuery);
-                        for (const alias of query_aliases) {
-                            pushTo.push(alias);
+                const idDuplicates = new Set();
+                const pathDuplicates = new Set();
+                let count = 0;
+                const prefixResults = [];
+                const normalizedUserQuery = parsedQuery.userQuery
+                    .replace(/[_"]/g, "")
+                    .toLowerCase();
+                /**
+                 * @param {string} name
+                 * @param {number} alias
+                 * @param {number} dist
+                 * @param {number} index
+                 * @returns {Promise<rustdoc.PlainResultObject?>}
+                 */
+                const handleAlias = async(name, alias, dist, index) => {
+                    return {
+                        id: alias,
+                        dist,
+                        path_dist: 0,
+                        index,
+                        alias: name,
+                        is_alias: true,
+                        elems: [], // only used in type-based queries
+                        returned: [], // only used in type-based queries
+                        original: await this.getRow(alias),
+                    };
+                };
+                /**
+                 * @param {Promise<rustdoc.PlainResultObject|null>[]} data
+                 * @returns {AsyncGenerator<rustdoc.ResultObject, boolean>}
+                 */
+                const flush = async function* (data) {
+                    const satr = sortAndTransformResults(
+                        await Promise.all(data),
+                        null,
+                        currentCrate,
+                        pathDuplicates,
+                    );
+                    data.length = 0;
+                    for await (const processed of satr) {
+                        yield processed;
+                        count += 1;
+                        if ((count & 0x7F) === 0) {
+                            await yieldToBrowser();
+                        }
+                        if (count >= MAX_RESULTS) {
+                            return true;
                         }
                     }
-                }
-            }
-
-            // @ts-expect-error
-            const sortFunc = (aaa, bbb) => {
-                if (aaa.original.path < bbb.original.path) {
-                    return 1;
-                } else if (aaa.original.path === bbb.original.path) {
-                    return 0;
-                }
-                return -1;
-            };
-            // @ts-expect-error
-            crateAliases.sort(sortFunc);
-            aliases.sort(sortFunc);
-
-            // @ts-expect-error
-            const pushFunc = alias => {
-                // Cloning `alias` to prevent its fields to be updated.
-                alias = {...alias};
-                const res = buildHrefAndPath(alias);
-                alias.displayPath = pathSplitter(res[0]);
-                alias.fullPath = alias.displayPath + alias.name;
-                alias.href = res[1];
-
-                ret.others.unshift(alias);
-                if (ret.others.length > MAX_RESULTS) {
-                    ret.others.pop();
-                }
-            };
-
-            aliases.forEach(pushFunc);
-            // @ts-expect-error
-            crateAliases.forEach(pushFunc);
-        };
-
-        /**
-         * This function adds the given result into the provided `results` map if it matches the
-         * following condition:
-         *
-         * * If it is a "literal search" (`parsedQuery.literalSearch`), then `dist` must be 0.
-         * * If it is not a "literal search", `dist` must be <= `maxEditDistance`.
-         *
-         * The `results` map contains information which will be used to sort the search results:
-         *
-         * * `fullId` is an `integer`` used as the key of the object we use for the `results` map.
-         * * `id` is the index in the `searchIndex` array for this element.
-         * * `index` is an `integer`` used to sort by the position of the word in the item's name.
-         * * `dist` is the main metric used to sort the search results.
-         * * `path_dist` is zero if a single-component search query is used, otherwise it's the
-         *   distance computed for everything other than the last path component.
-         *
-         * @param {rustdoc.Results} results
-         * @param {number} fullId
-         * @param {number} id
-         * @param {number} index
-         * @param {number} dist
-         * @param {number} path_dist
-         * @param {number} maxEditDistance
-         */
-        function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) {
-            if (dist <= maxEditDistance || index !== -1) {
-                if (results.has(fullId)) {
-                    const result = results.get(fullId);
-                    if (result === undefined || result.dontValidate || result.dist <= dist) {
-                        return;
+                    return false;
+                };
+                const aliasResults = await index.search(normalizedUserQuery);
+                if (aliasResults) {
+                    for (const id of aliasResults.matches().entries()) {
+                        const [name, alias] = await Promise.all([
+                            this.getName(id),
+                            this.getAliasTarget(id),
+                        ]);
+                        if (name !== null &&
+                            alias !== null &&
+                            !idDuplicates.has(id) &&
+                            name.replace(/[_"]/g, "").toLowerCase() === normalizedUserQuery
+                        ) {
+                            prefixResults.push(handleAlias(name, alias, 0, 0));
+                            idDuplicates.add(id);
+                        }
                     }
                 }
-                // @ts-expect-error
-                results.set(fullId, {
-                    id: id,
-                    index: index,
-                    dontValidate: parsedQuery.literalSearch,
-                    dist: dist,
-                    path_dist: path_dist,
-                });
-            }
-        }
-
-        /**
-         * This function is called in case the query has more than one element. In this case, it'll
-         * try to match the items which validates all the elements. For `aa -> bb` will look for
-         * functions which have a parameter `aa` and has `bb` in its returned values.
-         *
-         * @param {rustdoc.Row} row
-         * @param {number} pos      - Position in the `searchIndex`.
-         * @param {rustdoc.Results} results
-         */
-        function handleArgs(row, pos, results) {
-            if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
-                return;
-            }
-            const rowType = row.type;
-            if (!rowType) {
-                return;
-            }
-
-            const tfpDist = compareTypeFingerprints(
-                row.id,
-                parsedQuery.typeFingerprint,
-            );
-            if (tfpDist === null) {
-                return;
-            }
-            // @ts-expect-error
-            if (results.size >= MAX_RESULTS && tfpDist > results.max_dist) {
-                return;
-            }
-
-            // If the result is too "bad", we return false and it ends this search.
-            if (!unifyFunctionTypes(
-                rowType.inputs,
-                parsedQuery.elems,
-                rowType.where_clause,
-                null,
-                // @ts-expect-error
-                mgens => {
-                    return unifyFunctionTypes(
-                        rowType.output,
-                        parsedQuery.returned,
-                        rowType.where_clause,
-                        mgens,
-                        checkTypeMgensForConflict,
-                        0, // unboxing depth
-                    );
-                },
-                0, // unboxing depth
-            )) {
-                return;
-            }
-
-            results.max_dist = Math.max(results.max_dist || 0, tfpDist);
-            addIntoResults(results, row.id, pos, 0, tfpDist, 0, Number.MAX_VALUE);
-        }
-
-        /**
-         * Compare the query fingerprint with the function fingerprint.
-         *
-         * @param {number} fullId - The function
-         * @param {Uint32Array} queryFingerprint - The query
-         * @returns {number|null} - Null if non-match, number if distance
-         *                          This function might return 0!
-         */
-        const compareTypeFingerprints = (fullId, queryFingerprint) => {
-            const fh0 = this.functionTypeFingerprint[fullId * 4];
-            const fh1 = this.functionTypeFingerprint[(fullId * 4) + 1];
-            const fh2 = this.functionTypeFingerprint[(fullId * 4) + 2];
-            const [qh0, qh1, qh2] = queryFingerprint;
-            // Approximate set intersection with bloom filters.
-            // This can be larger than reality, not smaller, because hashes have
-            // the property that if they've got the same value, they hash to the
-            // same thing. False positives exist, but not false negatives.
-            const [in0, in1, in2] = [fh0 & qh0, fh1 & qh1, fh2 & qh2];
-            // Approximate the set of items in the query but not the function.
-            // This might be smaller than reality, but cannot be bigger.
-            //
-            // | in_ | qh_ | XOR | Meaning                                          |
-            // | --- | --- | --- | ------------------------------------------------ |
-            // |  0  |  0  |  0  | Not present                                      |
-            // |  1  |  0  |  1  | IMPOSSIBLE because `in_` is `fh_ & qh_`          |
-            // |  1  |  1  |  0  | If one or both is false positive, false negative |
-            // |  0  |  1  |  1  | Since in_ has no false negatives, must be real   |
-            if ((in0 ^ qh0) || (in1 ^ qh1) || (in2 ^ qh2)) {
-                return null;
-            }
-            return this.functionTypeFingerprint[(fullId * 4) + 3];
-        };
-
-
-        const innerRunQuery = () => {
-            if (parsedQuery.foundElems === 1 && !parsedQuery.hasReturnArrow) {
+                if (parsedQuery.error !== null || parsedQuery.elems.length === 0) {
+                    yield* flush(prefixResults);
+                    return;
+                }
                 const elem = parsedQuery.elems[0];
-                // use arrow functions to preserve `this`.
-                /** @type {function(number): void} */
-                const handleNameSearch = id => {
-                    const row = this.searchIndex[id];
-                    if (!typePassesFilter(elem.typeFilter, row.ty) ||
+                const typeFilter = itemTypeFromName(elem.typeFilter);
+                /**
+                 * @param {number} id
+                 * @returns {Promise<rustdoc.PlainResultObject?>}
+                 */
+                const handleNameSearch = async id => {
+                    const row = await this.getRow(id);
+                    if (!row || !row.entry) {
+                        return null;
+                    }
+                    if (!typePassesFilter(typeFilter, row.ty) ||
                         (filterCrates !== null && row.crate !== filterCrates)) {
-                        return;
+                        return null;
                     }
 
+                    /** @type {number|null} */
                     let pathDist = 0;
                     if (elem.fullPath.length > 1) {
-
-                        const maybePathDist = checkPath(elem.pathWithoutLast, row);
-                        if (maybePathDist === null) {
-                            return;
+                        pathDist = checkRowPath(elem.pathWithoutLast, row);
+                        if (pathDist === null) {
+                            return null;
                         }
-                        pathDist = maybePathDist;
                     }
 
                     if (parsedQuery.literalSearch) {
-                        if (row.word === elem.pathLast) {
-                            addIntoResults(results_others, row.id, id, 0, 0, pathDist, 0);
-                        }
+                        return row.name.toLowerCase() === elem.pathLast ? {
+                            id,
+                            dist: 0,
+                            path_dist: 0,
+                            index: 0,
+                            elems: [], // only used in type-based queries
+                            returned: [], // only used in type-based queries
+                            is_alias: false,
+                        } : null;
                     } else {
-                        addIntoResults(
-                            results_others,
-                            row.id,
+                        return {
                             id,
-                            row.normalizedName.indexOf(elem.normalizedPathLast),
-                            editDistance(
+                            dist: editDistance(
                                 row.normalizedName,
                                 elem.normalizedPathLast,
                                 maxEditDistance,
                             ),
-                            pathDist,
-                            maxEditDistance,
-                        );
+                            path_dist: pathDist,
+                            index: row.normalizedName.indexOf(elem.normalizedPathLast),
+                            elems: [], // only used in type-based queries
+                            returned: [], // only used in type-based queries
+                            is_alias: false,
+                        };
                     }
                 };
-                if (elem.normalizedPathLast !== "") {
-                    const last = elem.normalizedPathLast;
-                    for (const id of this.nameTrie.search(last, this.tailTable)) {
-                        handleNameSearch(id);
+                if (elem.normalizedPathLast === "") {
+                    // faster full-table scan for this specific case.
+                    const nameData = this.database.getData("name");
+                    const l = nameData ? nameData.length : 0;
+                    for (let id = 0; id < l; ++id) {
+                        if (!idDuplicates.has(id)) {
+                            idDuplicates.add(id);
+                            prefixResults.push(handleNameSearch(id));
+                        }
+                        if (yield* flush(prefixResults)) {
+                            return;
+                        }
                     }
+                    return;
                 }
-                const length = this.searchIndex.length;
-
-                for (let i = 0, nSearchIndex = length; i < nSearchIndex; ++i) {
-                    // queries that end in :: bypass the trie
-                    if (elem.normalizedPathLast === "") {
-                        handleNameSearch(i);
+                const results = await index.search(elem.normalizedPathLast);
+                if (results) {
+                    for await (const result of results.prefixMatches()) {
+                        for (const id of result.entries()) {
+                            if (!idDuplicates.has(id)) {
+                                idDuplicates.add(id);
+                                prefixResults.push(handleNameSearch(id));
+                                const [name, alias] = await Promise.all([
+                                    this.getName(id),
+                                    this.getAliasTarget(id),
+                                ]);
+                                if (name !== null && alias !== null) {
+                                    prefixResults.push(handleAlias(name, alias, 0, 0));
+                                }
+                            }
+                        }
+                        if (yield* flush(prefixResults)) {
+                            return;
+                        }
                     }
-                    const row = this.searchIndex[i];
-                    if (filterCrates !== null && row.crate !== filterCrates) {
-                        continue;
+                    if (yield* flush(prefixResults)) {
+                        return;
                     }
-                    const tfpDist = compareTypeFingerprints(
-                        row.id,
-                        parsedQuery.typeFingerprint,
-                    );
-                    if (tfpDist !== null) {
-                        const in_args = row.type && row.type.inputs
-                            && checkIfInList(row.type.inputs, elem, row.type.where_clause, null, 0);
-                        const returned = row.type && row.type.output
-                            && checkIfInList(row.type.output, elem, row.type.where_clause, null, 0);
-                        if (in_args) {
-                            results_in_args.max_dist = Math.max(
-                                results_in_args.max_dist || 0,
-                                tfpDist,
-                            );
-                            const maxDist = results_in_args.size < MAX_RESULTS ?
-                                (tfpDist + 1) :
-                                results_in_args.max_dist;
-                            addIntoResults(results_in_args, row.id, i, -1, tfpDist, 0, maxDist);
-                        }
-                        if (returned) {
-                            results_returned.max_dist = Math.max(
-                                results_returned.max_dist || 0,
-                                tfpDist,
-                            );
-                            const maxDist = results_returned.size < MAX_RESULTS ?
-                                (tfpDist + 1) :
-                                results_returned.max_dist;
-                            addIntoResults(results_returned, row.id, i, -1, tfpDist, 0, maxDist);
+                }
+                const levSearchResults = index.searchLev(elem.normalizedPathLast);
+                const levResults = [];
+                for await (const levResult of levSearchResults) {
+                    for (const id of levResult.matches().entries()) {
+                        if (!idDuplicates.has(id)) {
+                            idDuplicates.add(id);
+                            levResults.push(handleNameSearch(id));
+                            const [name, alias] = await Promise.all([
+                                this.getName(id),
+                                this.getAliasTarget(id),
+                            ]);
+                            if (name !== null && alias !== null) {
+                                levResults.push(handleAlias(
+                                    name,
+                                    alias,
+                                    editDistance(elem.normalizedPathLast, name, maxEditDistance),
+                                    name.indexOf(elem.normalizedPathLast),
+                                ));
+                            }
                         }
                     }
                 }
-            } else if (parsedQuery.foundElems > 0) {
-                // Sort input and output so that generic type variables go first and
-                // types with generic parameters go last.
-                // That's because of the way unification is structured: it eats off
-                // the end, and hits a fast path if the last item is a simple atom.
-                /** @type {function(rustdoc.QueryElement, rustdoc.QueryElement): number} */
-                const sortQ = (a, b) => {
-                    const ag = a.generics.length === 0 && a.bindings.size === 0;
-                    const bg = b.generics.length === 0 && b.bindings.size === 0;
-                    if (ag !== bg) {
-                        // unary `+` converts booleans into integers.
-                        return +ag - +bg;
+                yield* flush(levResults);
+                if (results) {
+                    const substringResults = [];
+                    for await (const result of results.substringMatches()) {
+                        for (const id of result.entries()) {
+                            if (!idDuplicates.has(id)) {
+                                idDuplicates.add(id);
+                                substringResults.push(handleNameSearch(id));
+                                const [name, alias] = await Promise.all([
+                                    this.getName(id),
+                                    this.getAliasTarget(id),
+                                ]);
+                                if (name !== null && alias !== null) {
+                                    levResults.push(handleAlias(
+                                        name,
+                                        alias,
+                                        editDistance(
+                                            elem.normalizedPathLast,
+                                            name,
+                                            maxEditDistance,
+                                        ),
+                                        name.indexOf(elem.normalizedPathLast),
+                                    ));
+                                }
+                            }
+                        }
+                        if (yield* flush(substringResults)) {
+                            return;
+                        }
                     }
-                    const ai = a.id !== null && a.id > 0;
-                    const bi = b.id !== null && b.id > 0;
-                    return +ai - +bi;
-                };
-                parsedQuery.elems.sort(sortQ);
-                parsedQuery.returned.sort(sortQ);
-                for (let i = 0, nSearchIndex = this.searchIndex.length; i < nSearchIndex; ++i) {
-                    handleArgs(this.searchIndex[i], i, results_others);
                 }
             }
-        };
+            .bind(this);
 
-        if (parsedQuery.error === null) {
-            innerRunQuery();
-        }
+        const innerRunTypeQuery =
+            /**
+             * @this {DocSearch}
+             * @param {rustdoc.ParserQueryElement[]} inputs
+             * @param {rustdoc.ParserQueryElement[]} output
+             * @param {"sig"|"elems"|"returned"|null} typeInfo
+             * @param {string} currentCrate
+             * @returns {AsyncGenerator<rustdoc.ResultObject>}
+             */
+            async function*(inputs, output, typeInfo, currentCrate) {
+                const index = this.database.getIndex("normalizedName");
+                if (!index) {
+                    return;
+                }
+                /** @type {Map<string, number>} */
+                const genericMap = new Map();
+                /**
+                 * @template Q
+                 * @typedef {{
+                 *     invertedIndex: stringdex.RoaringBitmap[],
+                 *     queryElem: Q,
+                 * }} PostingsList
+                 */
+                /** @type {stringdex.RoaringBitmap[]} */
+                const empty_inverted_index = [];
+                /** @type {PostingsList<any>[]} */
+                const empty_postings_list = [];
+                /** @type {stringdex.RoaringBitmap[]} */
+                const everything_inverted_index = [];
+                for (let i = 0; i < 64; ++i) {
+                    everything_inverted_index.push(RoaringBitmap.everything());
+                }
+                /**
+                 * @type {PostingsList<rustdoc.QueryElement[]>}
+                 */
+                const everything_postings_list = {
+                    invertedIndex: everything_inverted_index,
+                    queryElem: [],
+                };
+                /**
+                 * @type {PostingsList<rustdoc.QueryElement[]>[]}
+                 */
+                const nested_everything_postings_list = [everything_postings_list];
+                /**
+                 * @param {...stringdex.RoaringBitmap[]} idx
+                 * @returns {stringdex.RoaringBitmap[]}
+                 */
+                const intersectInvertedIndexes = (...idx) => {
+                    let i = 0;
+                    const l = idx.length;
+                    while (i < l - 1 && idx[i] === everything_inverted_index) {
+                        i += 1;
+                    }
+                    const result = [...idx[i]];
+                    for (; i < l; ++i) {
+                        if (idx[i] === everything_inverted_index) {
+                            continue;
+                        }
+                        if (idx[i].length < result.length) {
+                            result.length = idx[i].length;
+                        }
+                        for (let j = 0; j < result.length; ++j) {
+                            result[j] = result[j].intersection(idx[i][j]);
+                        }
+                    }
+                    return result;
+                };
+                /**
+                 * Fetch a bitmap of potentially-matching functions,
+                 * plus a list of query elements annotated with the correct IDs.
+                 *
+                 * More than one ID can exist because, for example, q=`Iter` can match
+                 * `std::vec::Iter`, or `std::btree_set::Iter`, or anything else, and those
+                 * items different IDs. What's worse, q=`Iter<Iter>` has N**2 possible
+                 * matches, because it could be `vec::Iter<btree_set::Iter>`,
+                 * `btree_set::Iter<vec::Iter>`, `vec::Iter<vec::Iter>`,
+                 * `btree_set::Iter<btree_set::Iter>`,
+                 * or anything else. This function returns all possible permutations.
+                 *
+                 * @param {rustdoc.ParserQueryElement|null} elem
+                 * @returns {Promise<PostingsList<rustdoc.QueryElement>[]>}
+                 */
+                const unpackPostingsList = async elem => {
+                    if (!elem) {
+                        return empty_postings_list;
+                    }
+                    const typeFilter = itemTypeFromName(elem.typeFilter);
+                    const searchResults = await index.search(elem.normalizedPathLast);
+                    /**
+                     * @type {Promise<[
+                     *     number,
+                     *     string|null,
+                     *     rustdoc.TypeData|null,
+                     *     rustdoc.PathData|null,
+                     * ]>[]}
+                     * */
+                    const typePromises = [];
+                    if (typeFilter !== TY_GENERIC && searchResults) {
+                        for (const id of searchResults.matches().entries()) {
+                            typePromises.push(Promise.all([
+                                this.getName(id),
+                                this.getTypeData(id),
+                                this.getPathData(id),
+                            ]).then(([name, typeData, pathData]) =>
+                                [id, name, typeData, pathData]));
+                        }
+                    }
+                    const types = (await Promise.all(typePromises))
+                        .filter(([_id, name, ty, path]) =>
+                            name !== null && name.toLowerCase() === elem.pathLast &&
+                            ty && !ty.invertedFunctionSignatureIndex.every(bitmap => {
+                                return bitmap.isEmpty();
+                            }) &&
+                            path && path.ty !== TY_ASSOCTYPE &&
+                            (elem.pathWithoutLast.length === 0 ||
+                                checkPath(
+                                    elem.pathWithoutLast,
+                                    path.modulePath.split("::"),
+                                ) === 0),
+                            );
+                    if (types.length === 0) {
+                        const areGenericsAllowed = typeFilter === TY_GENERIC || (
+                            typeFilter === -1 &&
+                            (parsedQuery.totalElems > 1 || parsedQuery.hasReturnArrow) &&
+                            elem.pathWithoutLast.length === 0 &&
+                            elem.generics.length === 0 &&
+                            elem.bindings.size === 0
+                        );
+                        if (typeFilter !== TY_GENERIC &&
+                            (elem.name.length >= 3 || !areGenericsAllowed)
+                        ) {
+                            /** @type {string|null} */
+                            let chosenName = null;
+                            /** @type {rustdoc.TypeData[]} */
+                            let chosenType = [];
+                            /** @type {rustdoc.PathData[]} */
+                            let chosenPath = [];
+                            /** @type {number[]} */
+                            let chosenId = [];
+                            let chosenDist = Number.MAX_SAFE_INTEGER;
+                            const levResults = index.searchLev(elem.normalizedPathLast);
+                            for await (const searchResults of levResults) {
+                                for (const id of searchResults.matches().entries()) {
+                                    const [name, ty, path] = await Promise.all([
+                                        this.getName(id),
+                                        this.getTypeData(id),
+                                        this.getPathData(id),
+                                    ]);
+                                    if (name !== null && ty !== null && path !== null &&
+                                        !ty.invertedFunctionSignatureIndex.every(bitmap => {
+                                            return bitmap.isEmpty();
+                                        }) &&
+                                        path.ty !== TY_ASSOCTYPE
+                                    ) {
+                                        let dist = editDistance(
+                                            name,
+                                            elem.pathLast,
+                                            maxEditDistance,
+                                        );
+                                        if (elem.pathWithoutLast.length !== 0) {
+                                            const pathDist = checkPath(
+                                                elem.pathWithoutLast,
+                                                path.modulePath.split("::"),
+                                            );
+                                            // guaranteed to be higher than the path limit
+                                            dist += pathDist === null ?
+                                                Number.MAX_SAFE_INTEGER :
+                                                pathDist;
+                                        }
+                                        if (name === chosenName) {
+                                            chosenId.push(id);
+                                            chosenType.push(ty);
+                                            chosenPath.push(path);
+                                        } else if (dist < chosenDist) {
+                                            chosenName = name;
+                                            chosenId = [id];
+                                            chosenType = [ty];
+                                            chosenPath = [path];
+                                            chosenDist = dist;
+                                        }
+                                    }
+                                }
+                                if (chosenId.length !== 0) {
+                                    // searchLev returns results in order
+                                    // if we have working matches, we're done
+                                    break;
+                                }
+                            }
+                            if (areGenericsAllowed) {
+                                parsedQuery.proposeCorrectionFrom = elem.name;
+                                parsedQuery.proposeCorrectionTo = chosenName;
+                            } else {
+                                parsedQuery.correction = chosenName;
+                                for (let i = 0; i < chosenType.length; ++i) {
+                                    types.push([
+                                        chosenId[i],
+                                        chosenName,
+                                        chosenType[i],
+                                        chosenPath[i],
+                                    ]);
+                                }
+                            }
+                        }
+                        if (areGenericsAllowed) {
+                            let genericId = genericMap.get(elem.normalizedPathLast);
+                            if (genericId === undefined) {
+                                genericId = genericMap.size;
+                                genericMap.set(elem.normalizedPathLast, genericId);
+                            }
+                            return [{
+                                invertedIndex: await this.getGenericInvertedIndex(genericId),
+                                queryElem: {
+                                    name: elem.name,
+                                    id: (-genericId) - 1,
+                                    typeFilter: TY_GENERIC,
+                                    generics: [],
+                                    bindings: EMPTY_BINDINGS_MAP,
+                                    fullPath: elem.fullPath,
+                                    pathLast: elem.pathLast,
+                                    normalizedPathLast: elem.normalizedPathLast,
+                                    pathWithoutLast: elem.pathWithoutLast,
+                                },
+                            }];
+                        }
+                    }
+                    types.sort(([_i, name1, _t, pathData1], [_i2, name2, _t2, pathData2]) => {
+                        const p1 = !pathData1 ? "" : pathData1.modulePath;
+                        const p2 = !pathData2 ? "" : pathData2.modulePath;
+                        const n1 = name1 === null ? "" : name1;
+                        const n2 = name2 === null ? "" : name2;
+                        if (p1.length !== p2.length) {
+                            return p1.length > p2.length ? +1 : -1;
+                        }
+                        if (n1.length !== n2.length) {
+                            return n1.length > n2.length ? +1 : -1;
+                        }
+                        if (n1 !== n2) {
+                            return n1 > n2 ? +1 : -1;
+                        }
+                        if (p1 !== p2) {
+                            return p1 > p2 ? +1 : -1;
+                        }
+                        return 0;
+                    });
+                    /** @type {PostingsList<rustdoc.QueryElement>[]} */
+                    const results = [];
+                    for (const [id, _name, typeData] of types) {
+                        if (!typeData || typeData.invertedFunctionSignatureIndex.every(bitmap => {
+                            return bitmap.isEmpty();
+                        })) {
+                            continue;
+                        }
+                        const upla = await unpackPostingsListAll(elem.generics);
+                        const uplb = await unpackPostingsListBindings(elem.bindings);
+                        for (const {invertedIndex: genericsIdx, queryElem: generics} of upla) {
+                            for (const {invertedIndex: bindingsIdx, queryElem: bindings} of uplb) {
+                                results.push({
+                                    invertedIndex: intersectInvertedIndexes(
+                                        typeData.invertedFunctionSignatureIndex,
+                                        genericsIdx,
+                                        bindingsIdx,
+                                    ),
+                                    queryElem: {
+                                        name: elem.name,
+                                        id,
+                                        typeFilter,
+                                        generics,
+                                        bindings,
+                                        fullPath: elem.fullPath,
+                                        pathLast: elem.pathLast,
+                                        normalizedPathLast: elem.normalizedPathLast,
+                                        pathWithoutLast: elem.pathWithoutLast,
+                                    },
+                                });
+                                if ((results.length & 0x7F) === 0) {
+                                    await yieldToBrowser();
+                                }
+                            }
+                        }
+                    }
+                    return results;
+                };
+                /**
+                 * Fetch all possible matching permutations of a list of query elements.
+                 *
+                 * The empty list returns an "identity postings list", with a bitmap that
+                 * matches everything and an empty list of elems. This allows you to safely
+                 * take the intersection of this bitmap.
+                 *
+                 * @param {(rustdoc.ParserQueryElement|null)[]|null} elems
+                 * @returns {Promise<PostingsList<rustdoc.QueryElement[]>[]>}
+                 */
+                const unpackPostingsListAll = async elems => {
+                    if (!elems || elems.length === 0) {
+                        return nested_everything_postings_list;
+                    }
+                    const [firstPostingsList, remainingAll] = await Promise.all([
+                        unpackPostingsList(elems[0]),
+                        unpackPostingsListAll(elems.slice(1)),
+                    ]);
+                    /** @type {PostingsList<rustdoc.QueryElement[]>[]} */
+                    const results = [];
+                    for (const {
+                        invertedIndex: firstIdx,
+                        queryElem: firstElem,
+                    } of firstPostingsList) {
+                        for (const {
+                            invertedIndex: remainingIdx,
+                            queryElem: remainingElems,
+                        } of remainingAll) {
+                            results.push({
+                                invertedIndex: intersectInvertedIndexes(firstIdx, remainingIdx),
+                                queryElem: [firstElem, ...remainingElems],
+                            });
+                            if ((results.length & 0x7F) === 0) {
+                                await yieldToBrowser();
+                            }
+                        }
+                    }
+                    return results;
+                };
+                /**
+                 * Fetch all possible matching permutations of a map query element bindings.
+                 *
+                 * The empty list returns an "identity postings list", with a bitmap that
+                 * matches everything and an empty list of elems. This allows you to safely
+                 * take the intersection of this bitmap.
+                 *
+                 * Heads up! This function mutates the Map that you provide.
+                 * Before passing an actual parser item to it, make sure to clone the map.
+                 *
+                 * @param {Map<string, rustdoc.ParserQueryElement[]>} elems
+                 * @returns {Promise<PostingsList<
+                 *     Map<number, rustdoc.QueryElement[]>,
+                 * >[]>}
+                 */
+                const unpackPostingsListBindings = async elems => {
+                    if (!elems) {
+                        return [{
+                            invertedIndex: everything_inverted_index,
+                            queryElem: new Map(),
+                        }];
+                    }
+                    const firstKey = elems.keys().next().value;
+                    if (firstKey === undefined) {
+                        return [{
+                            invertedIndex: everything_inverted_index,
+                            queryElem: new Map(),
+                        }];
+                    }
+                    const firstList = elems.get(firstKey);
+                    if (firstList === undefined) {
+                        return [{
+                            invertedIndex: everything_inverted_index,
+                            queryElem: new Map(),
+                        }];
+                    }
+                    const firstKeyIds = await index.search(firstKey);
+                    if (!firstKeyIds) {
+                        // User specified a non-existent key.
+                        return [{
+                            invertedIndex: empty_inverted_index,
+                            queryElem: new Map(),
+                        }];
+                    }
+                    elems.delete(firstKey);
+                    const [firstPostingsList, remainingAll] = await Promise.all([
+                        unpackPostingsListAll(firstList),
+                        unpackPostingsListBindings(elems),
+                    ]);
+                    /** @type {PostingsList<Map<number, rustdoc.QueryElement[]>>[]} */
+                    const results = [];
+                    for (const keyId of firstKeyIds.matches().entries()) {
+                        for (const {
+                            invertedIndex: firstIdx,
+                            queryElem: firstElem,
+                        } of firstPostingsList) {
+                            for (const {
+                                invertedIndex: remainingIdx,
+                                queryElem: remainingElems,
+                            } of remainingAll) {
+                                const elems = new Map(remainingElems);
+                                elems.set(keyId, firstElem);
+                                results.push({
+                                    invertedIndex: intersectInvertedIndexes(firstIdx, remainingIdx),
+                                    queryElem: elems,
+                                });
+                                if ((results.length & 0x7F) === 0) {
+                                    await yieldToBrowser();
+                                }
+                            }
+                        }
+                    }
+                    elems.set(firstKey, firstList);
+                    if (results.length === 0) {
+                        // User specified a non-existent key.
+                        return [{
+                            invertedIndex: empty_inverted_index,
+                            queryElem: new Map(),
+                        }];
+                    }
+                    return results;
+                };
 
-        const isType = parsedQuery.foundElems !== 1 || parsedQuery.hasReturnArrow;
-        const [sorted_in_args, sorted_returned, sorted_others] = await Promise.all([
-            sortResults(results_in_args, "elems", currentCrate),
-            sortResults(results_returned, "returned", currentCrate),
-            // @ts-expect-error
-            sortResults(results_others, (isType ? "query" : null), currentCrate),
-        ]);
-        const ret = createQueryResults(
-            sorted_in_args,
-            sorted_returned,
-            sorted_others,
-            parsedQuery);
-        await handleAliases(ret, parsedQuery.userQuery.replace(/"/g, ""),
-            filterCrates, currentCrate);
-        await Promise.all([ret.others, ret.returned, ret.in_args].map(async list => {
-            const descs = await Promise.all(list.map(result => {
-                // @ts-expect-error
-                return this.searchIndexEmptyDesc.get(result.crate).contains(result.bitIndex) ?
-                    "" :
-                    // @ts-expect-error
-                    this.searchState.loadDesc(result);
-            }));
-            for (const [i, result] of list.entries()) {
-                // @ts-expect-error
-                result.desc = descs[i];
+                // finally, we can do the actual unification loop
+                const [allInputs, allOutput] = await Promise.all([
+                    unpackPostingsListAll(inputs),
+                    unpackPostingsListAll(output),
+                ]);
+                let checkCounter = 0;
+                /**
+                 * Finally, we can perform an incremental search, sorted by the number of
+                 * entries that match a given query.
+                 *
+                 * The outer list gives the number of elements. The inner one is separate
+                 * for each distinct name resolution.
+                 *
+                 * @type {{
+                 *     bitmap: stringdex.RoaringBitmap,
+                 *     inputs: rustdoc.QueryElement[],
+                 *     output: rustdoc.QueryElement[],
+                 * }[][]}
+                 */
+                const queryPlan = [];
+                for (const {invertedIndex: inputsIdx, queryElem: inputs} of allInputs) {
+                    for (const {invertedIndex: outputIdx, queryElem: output} of allOutput) {
+                        const invertedIndex = intersectInvertedIndexes(inputsIdx, outputIdx);
+                        for (const [size, bitmap] of invertedIndex.entries()) {
+                            checkCounter += 1;
+                            if ((checkCounter & 0x7F) === 0) {
+                                await yieldToBrowser();
+                            }
+                            if (!queryPlan[size]) {
+                                queryPlan[size] = [];
+                            }
+                            queryPlan[size].push({
+                                bitmap, inputs, output,
+                            });
+                        }
+                    }
+                }
+                const resultPromises = [];
+                const dedup = new Set();
+                let resultCounter = 0;
+                const isReturnTypeQuery = inputs.length === 0;
+                /** @type {rustdoc.PlainResultObject[]} */
+                const pushToBottom = [];
+                plan: for (const queryStep of queryPlan) {
+                    for (const {bitmap, inputs, output} of queryStep) {
+                        for (const id of bitmap.entries()) {
+                            checkCounter += 1;
+                            if ((checkCounter & 0x7F) === 0) {
+                                await yieldToBrowser();
+                            }
+                            resultPromises.push(this.getFunctionData(id).then(async fnData => {
+                                if (!fnData || !fnData.functionSignature) {
+                                    return null;
+                                }
+                                checkCounter += 1;
+                                if ((checkCounter & 0x7F) === 0) {
+                                    await yieldToBrowser();
+                                }
+                                const functionSignature = fnData.functionSignature;
+                                if (!unifyFunctionTypes(
+                                    functionSignature.inputs,
+                                    inputs,
+                                    functionSignature.where_clause,
+                                    null,
+                                    mgens => {
+                                        return !!unifyFunctionTypes(
+                                            functionSignature.output,
+                                            output,
+                                            functionSignature.where_clause,
+                                            mgens,
+                                            checkTypeMgensForConflict,
+                                            0, // unboxing depth
+                                        );
+                                    },
+                                    0, // unboxing depth
+                                )) {
+                                    return null;
+                                }
+                                const result = {
+                                    id,
+                                    dist: fnData.elemCount,
+                                    path_dist: 0,
+                                    index: -1,
+                                    elems: inputs,
+                                    returned: output,
+                                    is_alias: false,
+                                };
+                                const entry = await this.getEntryData(id);
+                                if ((entry && !isFnLikeTy(entry.ty)) ||
+                                    (isReturnTypeQuery &&
+                                        functionSignature &&
+                                        containsTypeFromQuery(
+                                            output,
+                                            functionSignature.inputs,
+                                            functionSignature.where_clause,
+                                        )
+                                    )
+                                ) {
+                                    pushToBottom.push(result);
+                                    return null;
+                                }
+                                return result;
+                            }));
+                        }
+                    }
+                    for await (const result of sortAndTransformResults(
+                        await Promise.all(resultPromises),
+                        typeInfo,
+                        currentCrate,
+                        dedup,
+                    )) {
+                        if (resultCounter >= MAX_RESULTS) {
+                            break plan;
+                        }
+                        yield result;
+                        resultCounter += 1;
+                    }
+                    resultPromises.length = 0;
+                }
+                if (resultCounter >= MAX_RESULTS) {
+                    return;
+                }
+                for await (const result of sortAndTransformResults(
+                    await Promise.all(pushToBottom),
+                    typeInfo,
+                    currentCrate,
+                    dedup,
+                )) {
+                    if (resultCounter >= MAX_RESULTS) {
+                        break;
+                    }
+                    yield result;
+                    resultCounter += 1;
+                }
             }
-        }));
-        if (parsedQuery.error !== null && ret.others.length !== 0) {
-            // It means some doc aliases were found so let's "remove" the error!
-            ret.query.error = null;
+            .bind(this);
+
+        if (parsedQuery.foundElems === 1 && !parsedQuery.hasReturnArrow) {
+            // We never want the main tab to delay behind the other two tabs.
+            // This is a bit of a hack (because JS's scheduler doesn't have much of an API),
+            // along with making innerRunTypeQuery yield to the UI thread.
+            const {
+                promise: donePromise,
+                resolve: doneResolve,
+                reject: doneReject,
+            } = Promise.withResolvers();
+            const doneTimeout = timeout(250);
+            return {
+                "in_args": (async function*() {
+                    await Promise.race([donePromise, doneTimeout]);
+                    yield* innerRunTypeQuery(parsedQuery.elems, [], "elems", currentCrate);
+                })(),
+                "returned": (async function*() {
+                    await Promise.race([donePromise, doneTimeout]);
+                    yield* innerRunTypeQuery([], parsedQuery.elems, "returned", currentCrate);
+                })(),
+                "others": (async function*() {
+                    try {
+                        yield* innerRunNameQuery(currentCrate);
+                        doneResolve(null);
+                    } catch (e) {
+                        doneReject(e);
+                        throw e;
+                    }
+                })(),
+                "query": parsedQuery,
+            };
+        } else if (parsedQuery.error !== null) {
+            return {
+                "in_args": (async function*() {})(),
+                "returned": (async function*() {})(),
+                "others": innerRunNameQuery(currentCrate),
+                "query": parsedQuery,
+            };
+        } else {
+            const typeInfo = parsedQuery.elems.length === 0 ?
+                "returned" : (
+                    parsedQuery.returned.length === 0 ? "elems" : "sig"
+                );
+            return {
+                "in_args": (async function*() {})(),
+                "returned": (async function*() {})(),
+                "others": parsedQuery.foundElems === 0 ?
+                    (async function*() {})() :
+                    innerRunTypeQuery(
+                        parsedQuery.elems,
+                        parsedQuery.returned,
+                        typeInfo,
+                        currentCrate,
+                    ),
+                "query": parsedQuery,
+            };
         }
-        return ret;
     }
 }
 
 
 // ==================== Core search logic end ====================
 
-/** @type {Map<string, rustdoc.RawSearchIndexCrate>} */
-let rawSearchIndex;
-// @ts-expect-error
+/** @type {DocSearch} */
 let docSearch;
 const longItemTypes = [
     "keyword",
@@ -4762,12 +4631,8 @@ function buildUrl(search, filterCrates) {
 function getFilterCrates() {
     const elem = document.getElementById("crate-search");
 
-    if (elem &&
-        // @ts-expect-error
-        elem.value !== "all crates" &&
-        // @ts-expect-error
-        window.searchIndex.has(elem.value)
-    ) {
+    // @ts-expect-error
+    if (elem && elem.value !== "all crates") {
         // @ts-expect-error
         return elem.value;
     }
@@ -4777,8 +4642,7 @@ function getFilterCrates() {
 // @ts-expect-error
 function nextTab(direction) {
     const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length;
-    // @ts-expect-error
-    searchState.focusedByTab[searchState.currentTab] = document.activeElement;
+    window.searchState.focusedByTab[searchState.currentTab] = document.activeElement;
     printTab(next);
     focusSearchResult();
 }
@@ -4790,133 +4654,182 @@ function focusSearchResult() {
         document.querySelectorAll(".search-results.active a").item(0) ||
         document.querySelectorAll("#search-tabs button").item(searchState.currentTab);
     searchState.focusedByTab[searchState.currentTab] = null;
-    if (target) {
-        // @ts-expect-error
+    if (target && target instanceof HTMLElement) {
         target.focus();
     }
 }
 
 /**
  * Render a set of search results for a single tab.
- * @param {Array<?>}    array   - The search results for this tab
- * @param {rustdoc.ParsedQuery<rustdoc.QueryElement>} query
+ * @param {AsyncGenerator<rustdoc.ResultObject>} results   - The search results for this tab
+ * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query
  * @param {boolean}     display - True if this is the active tab
+ * @param {function(number, HTMLElement): any} finishedCallback
+ * @param {boolean} isTypeSearch
+ * @returns {Promise<HTMLElement>}
  */
-async function addTab(array, query, display) {
+async function addTab(results, query, display, finishedCallback, isTypeSearch) {
     const extraClass = display ? " active" : "";
 
-    const output = document.createElement(
-        array.length === 0 && query.error === null ? "div" : "ul",
-    );
-    if (array.length > 0) {
-        output.className = "search-results " + extraClass;
+    /** @type {HTMLElement} */
+    let output = document.createElement("ul");
+    output.className = "search-results " + extraClass;
 
-        const lis = Promise.all(array.map(async item => {
-            const name = item.is_alias ? item.original.name : item.name;
-            const type = itemTypes[item.ty];
-            const longType = longItemTypes[item.ty];
-            const typeName = longType.length !== 0 ? `${longType}` : "?";
+    let count = 0;
+
+    /** @type {Promise<string|null>[]} */
+    const descList = [];
 
-            const link = document.createElement("a");
-            link.className = "result-" + type;
-            link.href = item.href;
+    /** @param {rustdoc.ResultObject} obj */
+    const addNextResultToOutput = async obj => {
+        count += 1;
 
-            const resultName = document.createElement("span");
-            resultName.className = "result-name";
+        const name = obj.item.name;
+        const type = itemTypes[obj.item.ty];
+        const longType = longItemTypes[obj.item.ty];
+        const typeName = longType.length !== 0 ? `${longType}` : "?";
 
-            resultName.insertAdjacentHTML(
-                "beforeend",
-                `<span class="typename">${typeName}</span>`);
-            link.appendChild(resultName);
+        const link = document.createElement("a");
+        link.className = "result-" + type;
+        link.href = obj.href;
 
-            let alias = " ";
-            if (item.is_alias) {
-                alias = ` <div class="alias">\
-<b>${item.name}</b><i class="grey">&nbsp;- see&nbsp;</i>\
+        const resultName = document.createElement("span");
+        resultName.className = "result-name";
+
+        resultName.insertAdjacentHTML(
+            "beforeend",
+            `<span class="typename">${typeName}</span>`);
+        link.appendChild(resultName);
+
+        let alias = " ";
+        if (obj.alias !== undefined) {
+            alias = ` <div class="alias">\
+<b>${obj.alias}</b><i class="grey">&nbsp;- see&nbsp;</i>\
 </div>`;
-            }
-            resultName.insertAdjacentHTML(
-                "beforeend",
-                `<div class="path">${alias}\
-${item.displayPath}<span class="${type}">${name}</span>\
+        }
+        resultName.insertAdjacentHTML(
+            "beforeend",
+            `<div class="path">${alias}\
+${obj.displayPath}<span class="${type}">${name}</span>\
 </div>`);
 
-            const description = document.createElement("div");
-            description.className = "desc";
-            description.insertAdjacentHTML("beforeend", item.desc);
-            if (item.displayTypeSignature) {
-                const {type, mappedNames, whereClause} = await item.displayTypeSignature;
-                const displayType = document.createElement("div");
-                // @ts-expect-error
-                type.forEach((value, index) => {
-                    if (index % 2 !== 0) {
-                        const highlight = document.createElement("strong");
-                        highlight.appendChild(document.createTextNode(value));
-                        displayType.appendChild(highlight);
-                    } else {
-                        displayType.appendChild(document.createTextNode(value));
+        const description = document.createElement("div");
+        description.className = "desc";
+        obj.desc.then(desc => {
+            if (desc !== null) {
+                description.insertAdjacentHTML("beforeend", desc);
+            }
+        });
+        descList.push(obj.desc);
+        if (obj.displayTypeSignature) {
+            const {type, mappedNames, whereClause} = await obj.displayTypeSignature;
+            const displayType = document.createElement("div");
+            type.forEach((value, index) => {
+                if (index % 2 !== 0) {
+                    const highlight = document.createElement("strong");
+                    highlight.appendChild(document.createTextNode(value));
+                    displayType.appendChild(highlight);
+                } else {
+                    displayType.appendChild(document.createTextNode(value));
+                }
+            });
+            if (mappedNames.size > 0 || whereClause.size > 0) {
+                let addWhereLineFn = () => {
+                    const line = document.createElement("div");
+                    line.className = "where";
+                    line.appendChild(document.createTextNode("where"));
+                    displayType.appendChild(line);
+                    addWhereLineFn = () => {};
+                };
+                for (const [qname, name] of mappedNames) {
+                    // don't care unless the generic name is different
+                    if (name === qname) {
+                        continue;
                     }
-                });
-                if (mappedNames.size > 0 || whereClause.size > 0) {
-                    let addWhereLineFn = () => {
-                        const line = document.createElement("div");
-                        line.className = "where";
-                        line.appendChild(document.createTextNode("where"));
-                        displayType.appendChild(line);
-                        addWhereLineFn = () => {};
-                    };
-                    for (const [qname, name] of mappedNames) {
-                        // don't care unless the generic name is different
-                        if (name === qname) {
-                            continue;
-                        }
-                        addWhereLineFn();
-                        const line = document.createElement("div");
-                        line.className = "where";
-                        line.appendChild(document.createTextNode(`    ${qname} matches `));
-                        const lineStrong = document.createElement("strong");
-                        lineStrong.appendChild(document.createTextNode(name));
-                        line.appendChild(lineStrong);
-                        displayType.appendChild(line);
+                    addWhereLineFn();
+                    const line = document.createElement("div");
+                    line.className = "where";
+                    line.appendChild(document.createTextNode(`    ${qname} matches `));
+                    const lineStrong = document.createElement("strong");
+                    lineStrong.appendChild(document.createTextNode(name));
+                    line.appendChild(lineStrong);
+                    displayType.appendChild(line);
+                }
+                for (const [name, innerType] of whereClause) {
+                    // don't care unless there's at least one highlighted entry
+                    if (innerType.length <= 1) {
+                        continue;
                     }
-                    for (const [name, innerType] of whereClause) {
-                        // don't care unless there's at least one highlighted entry
-                        if (innerType.length <= 1) {
-                            continue;
+                    addWhereLineFn();
+                    const line = document.createElement("div");
+                    line.className = "where";
+                    line.appendChild(document.createTextNode(`    ${name}: `));
+                    innerType.forEach((value, index) => {
+                        if (index % 2 !== 0) {
+                            const highlight = document.createElement("strong");
+                            highlight.appendChild(document.createTextNode(value));
+                            line.appendChild(highlight);
+                        } else {
+                            line.appendChild(document.createTextNode(value));
                         }
-                        addWhereLineFn();
-                        const line = document.createElement("div");
-                        line.className = "where";
-                        line.appendChild(document.createTextNode(`    ${name}: `));
-                        // @ts-expect-error
-                        innerType.forEach((value, index) => {
-                            if (index % 2 !== 0) {
-                                const highlight = document.createElement("strong");
-                                highlight.appendChild(document.createTextNode(value));
-                                line.appendChild(highlight);
-                            } else {
-                                line.appendChild(document.createTextNode(value));
-                            }
-                        });
-                        displayType.appendChild(line);
-                    }
+                    });
+                    displayType.appendChild(line);
                 }
-                displayType.className = "type-signature";
-                link.appendChild(displayType);
             }
+            displayType.className = "type-signature";
+            link.appendChild(displayType);
+        }
 
-            link.appendChild(description);
-            return link;
-        }));
-        lis.then(lis => {
-            for (const li of lis) {
-                output.appendChild(li);
+        link.appendChild(description);
+        output.appendChild(link);
+
+        results.next().then(async nextResult => {
+            if (nextResult.value) {
+                addNextResultToOutput(nextResult.value);
+            } else {
+                await Promise.all(descList);
+                // need to make sure the element is shown before
+                // running this callback
+                yieldToBrowser().then(() => finishedCallback(count, output));
             }
         });
-    } else if (query.error === null) {
-        const dlroChannel = `https://doc.rust-lang.org/${getVar("channel")}`;
+    };
+    const firstResult = await results.next();
+    let correctionOutput = "";
+    if (query.correction !== null && isTypeSearch) {
+        const orig = query.returned.length > 0
+            ? query.returned[0].name
+            : query.elems[0].name;
+        correctionOutput = "<h3 class=\"search-corrections\">" +
+            `Type "${orig}" not found. ` +
+            "Showing results for closest type name " +
+            `"${query.correction}" instead.</h3>`;
+    }
+    if (query.proposeCorrectionFrom !== null && isTypeSearch) {
+        const orig = query.proposeCorrectionFrom;
+        const targ = query.proposeCorrectionTo;
+        correctionOutput = "<h3 class=\"search-corrections\">" +
+            `Type "${orig}" not found and used as generic parameter. ` +
+            `Consider searching for "${targ}" instead.</h3>`;
+    }
+    if (firstResult.value) {
+        if (correctionOutput !== "") {
+            const h3 = document.createElement("h3");
+            h3.innerHTML = correctionOutput;
+            output.appendChild(h3);
+        }
+        await addNextResultToOutput(firstResult.value);
+    } else {
+        output = document.createElement("div");
+        if (correctionOutput !== "") {
+            const h3 = document.createElement("h3");
+            h3.innerHTML = correctionOutput;
+            output.appendChild(h3);
+        }
         output.className = "search-failed" + extraClass;
-        output.innerHTML = "No results :(<br/>" +
+        const dlroChannel = `https://doc.rust-lang.org/${getVar("channel")}`;
+        if (query.userQuery !== "") {
+            output.innerHTML += "No results :(<br/>" +
             "Try on <a href=\"https://duckduckgo.com/?q=" +
             encodeURIComponent("rust " + query.userQuery) +
             "\">DuckDuckGo</a>?<br/><br/>" +
@@ -4929,192 +4842,198 @@ ${item.displayPath}<span class="${type}">${name}</span>\
             "introductions to language features and the language itself.</li><li><a " +
             "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
             " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
+        }
+        output.innerHTML += "Example searches:<ul>" +
+            "<li><a href=\"" + getNakedUrl() + "?search=std::vec\">std::vec</a></li>" +
+            "<li><a href=\"" + getNakedUrl() + "?search=u32+->+bool\">u32 -> bool</a></li>" +
+            "<li><a href=\"" + getNakedUrl() + "?search=Option<T>,+(T+->+U)+->+Option<U>\">" +
+                "Option&lt;T>, (T -> U) -> Option&lt;U></a></li>" +
+            "</ul>";
+        // need to make sure the element is shown before
+        // running this callback
+        yieldToBrowser().then(() => finishedCallback(0, output));
     }
     return output;
 }
 
-// @ts-expect-error
-function makeTabHeader(tabNb, text, nbElems) {
-    // https://blog.horizon-eda.org/misc/2020/02/19/ui.html
-    //
-    // CSS runs with `font-variant-numeric: tabular-nums` to ensure all
-    // digits are the same width. \u{2007} is a Unicode space character
-    // that is defined to be the same width as a digit.
-    const fmtNbElems =
-        nbElems < 10  ? `\u{2007}(${nbElems})\u{2007}\u{2007}` :
-        nbElems < 100 ? `\u{2007}(${nbElems})\u{2007}` : `\u{2007}(${nbElems})`;
-    if (searchState.currentTab === tabNb) {
-        return "<button class=\"selected\">" + text +
-            "<span class=\"count\">" + fmtNbElems + "</span></button>";
-    }
-    return "<button>" + text + "<span class=\"count\">" + fmtNbElems + "</span></button>";
+/**
+ * returns [tab, output]
+ * @param {number} tabNb
+ * @param {string} text
+ * @param {AsyncGenerator<rustdoc.ResultObject>} results
+ * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query
+ * @param {boolean} isTypeSearch
+ * @param {boolean} goToFirst
+ * @returns {[HTMLElement, Promise<HTMLElement>]}
+ */
+function makeTab(tabNb, text, results, query, isTypeSearch, goToFirst) {
+    const isCurrentTab = window.searchState.currentTab === tabNb;
+    const tabButton = document.createElement("button");
+    tabButton.appendChild(document.createTextNode(text));
+    tabButton.className = isCurrentTab ? "selected" : "";
+    const tabCount = document.createElement("span");
+    tabCount.className = "count loading";
+    tabCount.innerHTML = "\u{2007}(\u{2007})\u{2007}\u{2007}";
+    tabButton.appendChild(tabCount);
+    return [
+        tabButton,
+        addTab(results, query, isCurrentTab, (count, output) => {
+            const search = window.searchState.outputElement();
+            const error = query.error;
+            if (count === 0 && error !== null && search) {
+                error.forEach((value, index) => {
+                    value = value.split("<").join("&lt;").split(">").join("&gt;");
+                    if (index % 2 !== 0) {
+                        error[index] = `<code>${value.replaceAll(" ", "&nbsp;")}</code>`;
+                    } else {
+                        error[index] = value;
+                    }
+                });
+                const errorReport = document.createElement("h3");
+                errorReport.className = "error";
+                errorReport.innerHTML = `Query parser error: "${error.join("")}".`;
+                search.insertBefore(errorReport, search.firstElementChild);
+            } else if (goToFirst ||
+                (count === 1 && getSettingValue("go-to-only-result") === "true")
+            ) {
+                // Needed to force re-execution of JS when coming back to a page. Let's take this
+                // scenario as example:
+                //
+                // 1. You have the "Directly go to item in search if there is only one result"
+                //    option enabled.
+                // 2. You make a search which results only one result, leading you automatically to
+                //    this result.
+                // 3. You go back to previous page.
+                //
+                // Now, without the call below, the JS will not be re-executed and the previous
+                // state will be used, starting search again since the search input is not empty,
+                // leading you back to the previous page again.
+                window.onunload = () => { };
+                window.searchState.removeQueryParameters();
+                const a = output.querySelector("a");
+                if (a) {
+                    a.click();
+                    return;
+                }
+            }
+
+            // https://blog.horizon-eda.org/misc/2020/02/19/ui.html
+            //
+            // CSS runs with `font-variant-numeric: tabular-nums` to ensure all
+            // digits are the same width. \u{2007} is a Unicode space character
+            // that is defined to be the same width as a digit.
+            const fmtNbElems =
+                count < 10  ? `\u{2007}(${count})\u{2007}\u{2007}` :
+                count < 100 ? `\u{2007}(${count})\u{2007}` : `\u{2007}(${count})`;
+            tabCount.innerHTML = fmtNbElems;
+            tabCount.className = "count";
+        }, isTypeSearch),
+    ];
 }
 
 /**
+ * @param {DocSearch} docSearch
  * @param {rustdoc.ResultsTable} results
- * @param {boolean} go_to_first
+ * @param {boolean} goToFirst
  * @param {string} filterCrates
  */
-async function showResults(results, go_to_first, filterCrates) {
-    const search = searchState.outputElement();
-    if (go_to_first || (results.others.length === 1
-        && getSettingValue("go-to-only-result") === "true")
-    ) {
-        // Needed to force re-execution of JS when coming back to a page. Let's take this
-        // scenario as example:
-        //
-        // 1. You have the "Directly go to item in search if there is only one result" option
-        //    enabled.
-        // 2. You make a search which results only one result, leading you automatically to
-        //    this result.
-        // 3. You go back to previous page.
-        //
-        // Now, without the call below, the JS will not be re-executed and the previous state
-        // will be used, starting search again since the search input is not empty, leading you
-        // back to the previous page again.
-        window.onunload = () => { };
-        searchState.removeQueryParameters();
-        const elem = document.createElement("a");
-        elem.href = results.others[0].href;
-        removeClass(elem, "active");
-        // For firefox, we need the element to be in the DOM so it can be clicked.
-        document.body.appendChild(elem);
-        elem.click();
-        return;
-    }
-    if (results.query === undefined) {
-        // @ts-expect-error
-        results.query = DocSearch.parseQuery(searchState.input.value);
-    }
+async function showResults(docSearch, results, goToFirst, filterCrates) {
+    const search = window.searchState.outputElement();
 
-    currentResults = results.query.userQuery;
-
-    // Navigate to the relevant tab if the current tab is empty, like in case users search
-    // for "-> String". If they had selected another tab previously, they have to click on
-    // it again.
-    let currentTab = searchState.currentTab;
-    if ((currentTab === 0 && results.others.length === 0) ||
-        (currentTab === 1 && results.in_args.length === 0) ||
-        (currentTab === 2 && results.returned.length === 0)) {
-        if (results.others.length !== 0) {
-            currentTab = 0;
-        } else if (results.in_args.length) {
-            currentTab = 1;
-        } else if (results.returned.length) {
-            currentTab = 2;
-        }
+    if (!search) {
+        return;
     }
 
     let crates = "";
-    if (rawSearchIndex.size > 1) {
-        crates = "<div class=\"sub-heading\"> in&nbsp;<div id=\"crate-search-div\">" +
+    const crateNames = await docSearch.getCrateNameList();
+    if (crateNames.length > 1) {
+        crates = "&nbsp;in&nbsp;<div id=\"crate-search-div\">" +
             "<select id=\"crate-search\"><option value=\"all crates\">all crates</option>";
-        for (const c of rawSearchIndex.keys()) {
+        const l = crateNames.length;
+        for (let i = 0; i < l; i += 1) {
+            const c = crateNames[i];
             crates += `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`;
         }
-        crates += "</select></div></div>";
+        crates += "</select></div>";
     }
+    nonnull(document.querySelector(".search-switcher")).innerHTML = `Search results${crates}`;
 
-    let output = `<div class="main-heading">\
-        <h1 class="search-results-title">Results</h1>${crates}</div>`;
+    /** @type {[HTMLElement, Promise<HTMLElement>][]} */
+    const tabs = [];
+    searchState.currentTab = 0;
     if (results.query.error !== null) {
-        const error = results.query.error;
-        // @ts-expect-error
-        error.forEach((value, index) => {
-            value = value.split("<").join("&lt;").split(">").join("&gt;");
-            if (index % 2 !== 0) {
-                error[index] = `<code>${value.replaceAll(" ", "&nbsp;")}</code>`;
-            } else {
-                error[index] = value;
-            }
-        });
-        output += `<h3 class="error">Query parser error: "${error.join("")}".</h3>`;
-        output += "<div id=\"search-tabs\">" +
-            makeTabHeader(0, "In Names", results.others.length) +
-            "</div>";
-        currentTab = 0;
-    } else if (results.query.foundElems <= 1 && results.query.returned.length === 0) {
-        output += "<div id=\"search-tabs\">" +
-            makeTabHeader(0, "In Names", results.others.length) +
-            makeTabHeader(1, "In Parameters", results.in_args.length) +
-            makeTabHeader(2, "In Return Types", results.returned.length) +
-            "</div>";
+        tabs.push(makeTab(0, "In Names", results.others, results.query, false, goToFirst));
+    } else if (
+        results.query.foundElems <= 1 &&
+        results.query.returned.length === 0 &&
+        !results.query.hasReturnArrow
+    ) {
+        tabs.push(makeTab(0, "In Names", results.others, results.query, false, goToFirst));
+        tabs.push(makeTab(1, "In Parameters", results.in_args, results.query, true, false));
+        tabs.push(makeTab(2, "In Return Types", results.returned, results.query, true, false));
     } else {
         const signatureTabTitle =
             results.query.elems.length === 0 ? "In Function Return Types" :
                 results.query.returned.length === 0 ? "In Function Parameters" :
                     "In Function Signatures";
-        output += "<div id=\"search-tabs\">" +
-            makeTabHeader(0, signatureTabTitle, results.others.length) +
-            "</div>";
-        currentTab = 0;
-    }
-
-    if (results.query.correction !== null) {
-        const orig = results.query.returned.length > 0
-            ? results.query.returned[0].name
-            : results.query.elems[0].name;
-        output += "<h3 class=\"search-corrections\">" +
-            `Type "${orig}" not found. ` +
-            "Showing results for closest type name " +
-            `"${results.query.correction}" instead.</h3>`;
-    }
-    if (results.query.proposeCorrectionFrom !== null) {
-        const orig = results.query.proposeCorrectionFrom;
-        const targ = results.query.proposeCorrectionTo;
-        output += "<h3 class=\"search-corrections\">" +
-            `Type "${orig}" not found and used as generic parameter. ` +
-            `Consider searching for "${targ}" instead.</h3>`;
+        tabs.push(makeTab(0, signatureTabTitle, results.others, results.query, true, goToFirst));
     }
 
-    const [ret_others, ret_in_args, ret_returned] = await Promise.all([
-        addTab(results.others, results.query, currentTab === 0),
-        addTab(results.in_args, results.query, currentTab === 1),
-        addTab(results.returned, results.query, currentTab === 2),
-    ]);
+    const tabsElem = document.createElement("div");
+    tabsElem.id = "search-tabs";
 
     const resultsElem = document.createElement("div");
     resultsElem.id = "results";
-    resultsElem.appendChild(ret_others);
-    resultsElem.appendChild(ret_in_args);
-    resultsElem.appendChild(ret_returned);
 
-    // @ts-expect-error
-    search.innerHTML = output;
-    if (searchState.rustdocToolbar) {
-        // @ts-expect-error
-        search.querySelector(".main-heading").appendChild(searchState.rustdocToolbar);
+    search.innerHTML = "";
+    for (const [tab, output] of tabs) {
+        tabsElem.appendChild(tab);
+        const placeholder = document.createElement("div");
+        output.then(output => {
+            if (placeholder.parentElement) {
+                placeholder.parentElement.replaceChild(output, placeholder);
+            }
+        });
+        resultsElem.appendChild(placeholder);
+    }
+
+    if (window.searchState.rustdocToolbar) {
+        nonnull(
+            nonnull(window.searchState.containerElement())
+                .querySelector(".main-heading"),
+        ).appendChild(window.searchState.rustdocToolbar);
     }
     const crateSearch = document.getElementById("crate-search");
     if (crateSearch) {
         crateSearch.addEventListener("input", updateCrate);
     }
-    // @ts-expect-error
+    search.appendChild(tabsElem);
     search.appendChild(resultsElem);
     // Reset focused elements.
-    searchState.showResults(search);
-    // @ts-expect-error
-    const elems = document.getElementById("search-tabs").childNodes;
-    // @ts-expect-error
-    searchState.focusedByTab = [];
+    window.searchState.showResults();
+    window.searchState.focusedByTab = [null, null, null];
     let i = 0;
-    for (const elem of elems) {
+    for (const elem of tabsElem.childNodes) {
         const j = i;
         // @ts-expect-error
         elem.onclick = () => printTab(j);
-        searchState.focusedByTab.push(null);
+        window.searchState.focusedByTab[i] = null;
         i += 1;
     }
-    printTab(currentTab);
+    printTab(0);
 }
 
 // @ts-expect-error
 function updateSearchHistory(url) {
+    const btn = document.querySelector("#search-button a");
+    if (btn instanceof HTMLAnchorElement) {
+        btn.href = url;
+    }
     if (!browserSupportsHistoryApi()) {
         return;
     }
     const params = searchState.getQueryStringParams();
-    if (!history.state && !params.search) {
+    if (!history.state && params.search === undefined) {
         history.pushState(null, "", url);
     } else {
         history.replaceState(null, "", url);
@@ -5127,8 +5046,8 @@ function updateSearchHistory(url) {
  * @param {boolean} [forced]
  */
 async function search(forced) {
-    // @ts-expect-error
-    const query = DocSearch.parseQuery(searchState.input.value.trim());
+    const query = DocSearch.parseQuery(nonnull(window.searchState.inputElement()).value.trim());
+
     let filterCrates = getFilterCrates();
 
     // @ts-expect-error
@@ -5138,6 +5057,7 @@ async function search(forced) {
         }
         return;
     }
+    currentResults = query.userQuery;
 
     searchState.setLoadingSearch();
 
@@ -5149,6 +5069,12 @@ async function search(forced) {
         filterCrates = params["filter-crate"];
     }
 
+    if (filterCrates !== null &&
+        (await docSearch.getCrateNameList()).indexOf(filterCrates) === -1
+    ) {
+        filterCrates = null;
+    }
+
     // Update document title to maintain a meaningful browser history
     searchState.title = "\"" + query.userQuery + "\" Search - Rust";
 
@@ -5157,6 +5083,7 @@ async function search(forced) {
     updateSearchHistory(buildUrl(query.userQuery, filterCrates));
 
     await showResults(
+        docSearch,
         // @ts-expect-error
         await docSearch.execQuery(query, filterCrates, window.currentCrate),
         params.go_to_first,
@@ -5176,16 +5103,14 @@ function onSearchSubmit(e) {
 }
 
 function putBackSearch() {
-    const search_input = searchState.input;
-    if (!searchState.input) {
+    const search_input = window.searchState.inputElement();
+    if (!search_input) {
         return;
     }
-    // @ts-expect-error
     if (search_input.value !== "" && !searchState.isDisplayed()) {
         searchState.showResults();
         if (browserSupportsHistoryApi()) {
             history.replaceState(null, "",
-                // @ts-expect-error
                 buildUrl(search_input.value, getFilterCrates()));
         }
         document.title = searchState.title;
@@ -5199,30 +5124,21 @@ function registerSearchEvents() {
     // but only if the input bar is empty. This avoid the obnoxious issue
     // where you start trying to do a search, and the index loads, and
     // suddenly your search is gone!
-    // @ts-expect-error
-    if (searchState.input.value === "") {
-        // @ts-expect-error
-        searchState.input.value = params.search || "";
+    const inputElement = nonnull(window.searchState.inputElement());
+    if (inputElement.value === "") {
+        inputElement.value = params.search || "";
     }
 
     const searchAfter500ms = () => {
         searchState.clearInputTimeout();
-        // @ts-expect-error
-        if (searchState.input.value.length === 0) {
-            searchState.hideResults();
-        } else {
-            // @ts-ignore
-            searchState.timeout = setTimeout(search, 500);
-        }
+        window.searchState.timeout = setTimeout(search, 500);
     };
-    // @ts-expect-error
-    searchState.input.onkeyup = searchAfter500ms;
-    // @ts-expect-error
-    searchState.input.oninput = searchAfter500ms;
-    // @ts-expect-error
-    document.getElementsByClassName("search-form")[0].onsubmit = onSearchSubmit;
-    // @ts-expect-error
-    searchState.input.onchange = e => {
+    inputElement.onkeyup = searchAfter500ms;
+    inputElement.oninput = searchAfter500ms;
+    if (inputElement.form) {
+        inputElement.form.onsubmit = onSearchSubmit;
+    }
+    inputElement.onchange = e => {
         if (e.target !== document.activeElement) {
             // To prevent doing anything when it's from a blur event.
             return;
@@ -5234,11 +5150,13 @@ function registerSearchEvents() {
         // change, though.
         setTimeout(search, 0);
     };
-    // @ts-expect-error
-    searchState.input.onpaste = searchState.input.onchange;
+    inputElement.onpaste = inputElement.onchange;
 
     // @ts-expect-error
     searchState.outputElement().addEventListener("keydown", e => {
+        if (!(e instanceof KeyboardEvent)) {
+            return;
+        }
         // We only handle unmodified keystrokes here. We don't want to interfere with,
         // for instance, alt-left and alt-right for history navigation.
         if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
@@ -5278,88 +5196,23 @@ function registerSearchEvents() {
         }
     });
 
-    // @ts-expect-error
-    searchState.input.addEventListener("keydown", e => {
+    inputElement.addEventListener("keydown", e => {
         if (e.which === 40) { // down
             focusSearchResult();
             e.preventDefault();
         }
     });
 
-    // @ts-expect-error
-    searchState.input.addEventListener("focus", () => {
+    inputElement.addEventListener("focus", () => {
         putBackSearch();
     });
-
-    // @ts-expect-error
-    searchState.input.addEventListener("blur", () => {
-        if (window.searchState.input) {
-            window.searchState.input.placeholder = window.searchState.origPlaceholder;
-        }
-    });
-
-    // Push and pop states are used to add search results to the browser
-    // history.
-    if (browserSupportsHistoryApi()) {
-        // Store the previous <title> so we can revert back to it later.
-        const previousTitle = document.title;
-
-        window.addEventListener("popstate", e => {
-            const params = searchState.getQueryStringParams();
-            // Revert to the previous title manually since the History
-            // API ignores the title parameter.
-            document.title = previousTitle;
-            // When browsing forward to search results the previous
-            // search will be repeated, so the currentResults are
-            // cleared to ensure the search is successful.
-            currentResults = null;
-            // Synchronize search bar with query string state and
-            // perform the search. This will empty the bar if there's
-            // nothing there, which lets you really go back to a
-            // previous state with nothing in the bar.
-            if (params.search && params.search.length > 0) {
-                // @ts-expect-error
-                searchState.input.value = params.search;
-                // Some browsers fire "onpopstate" for every page load
-                // (Chrome), while others fire the event only when actually
-                // popping a state (Firefox), which is why search() is
-                // called both here and at the end of the startSearch()
-                // function.
-                e.preventDefault();
-                search();
-            } else {
-                // @ts-expect-error
-                searchState.input.value = "";
-                // When browsing back from search results the main page
-                // visibility must be reset.
-                searchState.hideResults();
-            }
-        });
-    }
-
-    // This is required in firefox to avoid this problem: Navigating to a search result
-    // with the keyboard, hitting enter, and then hitting back would take you back to
-    // the doc page, rather than the search that should overlay it.
-    // This was an interaction between the back-forward cache and our handlers
-    // that try to sync state between the URL and the search input. To work around it,
-    // do a small amount of re-init on page show.
-    window.onpageshow = () => {
-        const qSearch = searchState.getQueryStringParams().search;
-        // @ts-expect-error
-        if (searchState.input.value === "" && qSearch) {
-            // @ts-expect-error
-            searchState.input.value = qSearch;
-        }
-        search();
-    };
 }
 
 // @ts-expect-error
 function updateCrate(ev) {
     if (ev.target.value === "all crates") {
         // If we don't remove it from the URL, it'll be picked up again by the search.
-        // @ts-expect-error
-        const query = searchState.input.value.trim();
+        const query = nonnull(window.searchState.inputElement()).value.trim();
         updateSearchHistory(buildUrl(query, null));
     }
     // In case you "cut" the entry from the search input, then change the crate filter
@@ -5369,522 +5222,89 @@ function updateCrate(ev) {
     search(true);
 }
 
-// Parts of this code are based on Lucene, which is licensed under the
-// Apache/2.0 license.
-// More information found here:
-// https://fossies.org/linux/lucene/lucene/core/src/java/org/apache/lucene/util/automaton/
-//   LevenshteinAutomata.java
-class ParametricDescription {
-    // @ts-expect-error
-    constructor(w, n, minErrors) {
-        this.w = w;
-        this.n = n;
-        this.minErrors = minErrors;
-    }
-    // @ts-expect-error
-    isAccept(absState) {
-        const state = Math.floor(absState / (this.w + 1));
-        const offset = absState % (this.w + 1);
-        return this.w - offset + this.minErrors[state] <= this.n;
-    }
-    // @ts-expect-error
-    getPosition(absState) {
-        return absState % (this.w + 1);
-    }
-    // @ts-expect-error
-    getVector(name, charCode, pos, end) {
-        let vector = 0;
-        for (let i = pos; i < end; i += 1) {
-            vector = vector << 1;
-            if (name.charCodeAt(i) === charCode) {
-                vector |= 1;
-            }
-        }
-        return vector;
-    }
-    // @ts-expect-error
-    unpack(data, index, bitsPerValue) {
-        const bitLoc = (bitsPerValue * index);
-        const dataLoc = bitLoc >> 5;
-        const bitStart = bitLoc & 31;
-        if (bitStart + bitsPerValue <= 32) {
-            // not split
-            return ((data[dataLoc] >> bitStart) & this.MASKS[bitsPerValue - 1]);
-        } else {
-            // split
-            const part = 32 - bitStart;
-            return ~~(((data[dataLoc] >> bitStart) & this.MASKS[part - 1]) +
-                ((data[1 + dataLoc] & this.MASKS[bitsPerValue - part - 1]) << part));
-        }
+// eslint-disable-next-line max-len
+// polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64
+/**
+ * @type {function(string): Uint8Array} base64
+ */
+//@ts-expect-error
+const makeUint8ArrayFromBase64 = Uint8Array.fromBase64 ? Uint8Array.fromBase64 : (string => {
+    const bytes_as_string = atob(string);
+    const l = bytes_as_string.length;
+    const bytes = new Uint8Array(l);
+    for (let i = 0; i < l; ++i) {
+        bytes[i] = bytes_as_string.charCodeAt(i);
     }
-}
-ParametricDescription.prototype.MASKS = new Int32Array([
-    0x1, 0x3, 0x7, 0xF,
-    0x1F, 0x3F, 0x7F, 0xFF,
-    0x1FF, 0x3F, 0x7FF, 0xFFF,
-    0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
-    0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF,
-    0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF,
-    0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF,
-    0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF,
-]);
-
-// The following code was generated with the moman/finenight pkg
-// This package is available under the MIT License, see NOTICE.txt
-// for more details.
-// This class is auto-generated, Please do not modify it directly.
-// You should modify the https://gitlab.com/notriddle/createAutomata.py instead.
-// The following code was generated with the moman/finenight pkg
-// This package is available under the MIT License, see NOTICE.txt
-// for more details.
-// This class is auto-generated, Please do not modify it directly.
-// You should modify https://gitlab.com/notriddle/moman-rustdoc instead.
-
-class Lev2TParametricDescription extends ParametricDescription {
-    /**
-     * @param {number} absState
-     * @param {number} position
-     * @param {number} vector
-     * @returns {number}
-    */
-    transition(absState, position, vector) {
-        let state = Math.floor(absState / (this.w + 1));
-        let offset = absState % (this.w + 1);
-
-        if (position === this.w) {
-            if (state < 3) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 3) + state;
-                offset += this.unpack(this.offsetIncrs0, loc, 1);
-                state = this.unpack(this.toStates0, loc, 2) - 1;
-            }
-        } else if (position === this.w - 1) {
-            if (state < 5) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 5) + state;
-                offset += this.unpack(this.offsetIncrs1, loc, 1);
-                state = this.unpack(this.toStates1, loc, 3) - 1;
-            }
-        } else if (position === this.w - 2) {
-            if (state < 13) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 13) + state;
-                offset += this.unpack(this.offsetIncrs2, loc, 2);
-                state = this.unpack(this.toStates2, loc, 4) - 1;
-            }
-        } else if (position === this.w - 3) {
-            if (state < 28) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 28) + state;
-                offset += this.unpack(this.offsetIncrs3, loc, 2);
-                state = this.unpack(this.toStates3, loc, 5) - 1;
-            }
-        } else if (position === this.w - 4) {
-            if (state < 45) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 45) + state;
-                offset += this.unpack(this.offsetIncrs4, loc, 3);
-                state = this.unpack(this.toStates4, loc, 6) - 1;
-            }
-        } else {
-            if (state < 45) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 45) + state;
-                offset += this.unpack(this.offsetIncrs5, loc, 3);
-                state = this.unpack(this.toStates5, loc, 6) - 1;
-            }
-        }
+    return bytes;
+});
 
-        if (state === -1) {
-            // null state
-            return -1;
-        } else {
-            // translate back to abs
-            return Math.imul(state, this.w + 1) + offset;
-        }
-    }
 
-    // state map
-    //   0 -> [(0, 0)]
-    //   1 -> [(0, 1)]
-    //   2 -> [(0, 2)]
-    //   3 -> [(0, 1), (1, 1)]
-    //   4 -> [(0, 2), (1, 2)]
-    //   5 -> [(0, 1), (1, 1), (2, 1)]
-    //   6 -> [(0, 2), (1, 2), (2, 2)]
-    //   7 -> [(0, 1), (2, 1)]
-    //   8 -> [(0, 1), (2, 2)]
-    //   9 -> [(0, 2), (2, 1)]
-    //   10 -> [(0, 2), (2, 2)]
-    //   11 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
-    //   12 -> [t(0, 2), (0, 2), (1, 2), (2, 2)]
-    //   13 -> [(0, 2), (1, 2), (2, 2), (3, 2)]
-    //   14 -> [(0, 1), (1, 1), (3, 2)]
-    //   15 -> [(0, 1), (2, 2), (3, 2)]
-    //   16 -> [(0, 1), (3, 2)]
-    //   17 -> [(0, 1), t(1, 2), (2, 2), (3, 2)]
-    //   18 -> [(0, 2), (1, 2), (3, 1)]
-    //   19 -> [(0, 2), (1, 2), (3, 2)]
-    //   20 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2)]
-    //   21 -> [(0, 2), (2, 1), (3, 1)]
-    //   22 -> [(0, 2), (2, 2), (3, 2)]
-    //   23 -> [(0, 2), (3, 1)]
-    //   24 -> [(0, 2), (3, 2)]
-    //   25 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2)]
-    //   26 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2)]
-    //   27 -> [t(0, 2), (0, 2), (1, 2), (3, 1)]
-    //   28 -> [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
-    //   29 -> [(0, 2), (1, 2), (2, 2), (4, 2)]
-    //   30 -> [(0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
-    //   31 -> [(0, 2), (1, 2), (3, 2), (4, 2)]
-    //   32 -> [(0, 2), (1, 2), (4, 2)]
-    //   33 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2), (4, 2)]
-    //   34 -> [(0, 2), (1, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
-    //   35 -> [(0, 2), (2, 1), (4, 2)]
-    //   36 -> [(0, 2), (2, 2), (3, 2), (4, 2)]
-    //   37 -> [(0, 2), (2, 2), (4, 2)]
-    //   38 -> [(0, 2), (3, 2), (4, 2)]
-    //   39 -> [(0, 2), (4, 2)]
-    //   40 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
-    //   41 -> [(0, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
-    //   42 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
-    //   43 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (4, 2)]
-    //   44 -> [t(0, 2), (0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
-
-
-    /** @param {number} w - length of word being checked */
-    constructor(w) {
-        super(w, 2, new Int32Array([
-            0,1,2,0,1,-1,0,-1,0,-1,0,-1,0,-1,-1,-1,-1,-1,-2,-1,-1,-2,-1,-2,
-            -1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,
-        ]));
+if (ROOT_PATH === null) {
+    return;
+}
+const database = await Stringdex.loadDatabase(hooks);
+if (typeof window !== "undefined") {
+    docSearch = new DocSearch(ROOT_PATH, database);
+    await docSearch.buildIndex();
+    onEachLazy(document.querySelectorAll(
+        ".search-form.loading",
+    ), form => {
+        removeClass(form, "loading");
+    });
+    registerSearchEvents();
+    // If there's a search term in the URL, execute the search now.
+    if (window.searchState.getQueryStringParams().search !== undefined) {
+        search();
     }
+} else if (typeof exports !== "undefined") {
+    docSearch = new DocSearch(ROOT_PATH, database);
+    await docSearch.buildIndex();
+    return { docSearch, DocSearch };
 }
+};
 
-Lev2TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
-    0xe,
-]);
-Lev2TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
-    0x0,
-]);
-
-Lev2TParametricDescription.prototype.toStates1 = /*3 bits per value */ new Int32Array([
-    0x1a688a2c,
-]);
-Lev2TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
-    0x3e0,
-]);
-
-Lev2TParametricDescription.prototype.toStates2 = /*4 bits per value */ new Int32Array([
-    0x70707054,0xdc07035,0x3dd3a3a,0x2323213a,
-    0x15435223,0x22545432,0x5435,
-]);
-Lev2TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
-    0x80000,0x55582088,0x55555555,0x55,
-]);
-
-Lev2TParametricDescription.prototype.toStates3 = /*5 bits per value */ new Int32Array([
-    0x1c0380a4,0x700a570,0xca529c0,0x180a00,
-    0xa80af180,0xc5498e60,0x5a546398,0x8c4300e8,
-    0xac18c601,0xd8d43501,0x863500ad,0x51976d6a,
-    0x8ca0180a,0xc3501ac2,0xb0c5be16,0x76dda8a5,
-    0x18c4519,0xc41294a,0xe248d231,0x1086520c,
-    0xce31ac42,0x13946358,0x2d0348c4,0x6732d494,
-    0x1ad224a5,0xd635ad4b,0x520c4139,0xce24948,
-    0x22110a52,0x58ce729d,0xc41394e3,0x941cc520,
-    0x90e732d4,0x4729d224,0x39ce35ad,
-]);
-Lev2TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
-    0x80000,0xc0c830,0x300f3c30,0x2200fcff,
-    0xcaa00a08,0x3c2200a8,0xa8fea00a,0x55555555,
-    0x55555555,0x55555555,0x55555555,0x55555555,
-    0x55555555,0x55555555,
-]);
-
-Lev2TParametricDescription.prototype.toStates4 = /*6 bits per value */ new Int32Array([
-    0x801c0144,0x1453803,0x14700038,0xc0005145,
-    0x1401,0x14,0x140000,0x0,
-    0x510000,0x6301f007,0x301f00d1,0xa186178,
-    0xc20ca0c3,0xc20c30,0xc30030c,0xc00c00cd,
-    0xf0c00c30,0x4c054014,0xc30944c3,0x55150c34,
-    0x8300550,0x430c0143,0x50c31,0xc30850c,
-    0xc3143000,0x50053c50,0x5130d301,0x850d30c2,
-    0x30a08608,0xc214414,0x43142145,0x21450031,
-    0x1400c314,0x4c143145,0x32832803,0x28014d6c,
-    0xcd34a0c3,0x1c50c76,0x1c314014,0x430c30c3,
-    0x1431,0xc300500,0xca00d303,0xd36d0e40,
-    0x90b0e400,0xcb2abb2c,0x70c20ca1,0x2c32ca2c,
-    0xcd2c70cb,0x31c00c00,0x34c2c32c,0x5583280,
-    0x558309b7,0x6cd6ca14,0x430850c7,0x51c51401,
-    0x1430c714,0xc3087,0x71451450,0xca00d30,
-    0xc26dc156,0xb9071560,0x1cb2abb2,0xc70c2144,
-    0xb1c51ca1,0x1421c70c,0xc51c00c3,0x30811c51,
-    0x24324308,0xc51031c2,0x70820820,0x5c33830d,
-    0xc33850c3,0x30c30c30,0xc30c31c,0x451450c3,
-    0x20c20c20,0xda0920d,0x5145914f,0x36596114,
-    0x51965865,0xd9643653,0x365a6590,0x51964364,
-    0x43081505,0x920b2032,0x2c718b28,0xd7242249,
-    0x35cb28b0,0x2cb3872c,0x972c30d7,0xb0c32cb2,
-    0x4e1c75c,0xc80c90c2,0x62ca2482,0x4504171c,
-    0xd65d9610,0x33976585,0xd95cb5d,0x4b5ca5d7,
-    0x73975c36,0x10308138,0xc2245105,0x41451031,
-    0x14e24208,0xc35c3387,0x51453851,0x1c51c514,
-    0xc70c30c3,0x20451450,0x14f1440c,0x4f0da092,
-    0x4513d41,0x6533944d,0x1350e658,0xe1545055,
-    0x64365a50,0x5519383,0x51030815,0x28920718,
-    0x441c718b,0x714e2422,0x1c35cb28,0x4e1c7387,
-    0xb28e1c51,0x5c70c32c,0xc204e1c7,0x81c61440,
-    0x1c62ca24,0xd04503ce,0x85d63944,0x39338e65,
-    0x8e154387,0x364b5ca3,0x38739738,
-]);
-Lev2TParametricDescription.prototype.offsetIncrs4 = /*3 bits per value */ new Int32Array([
-    0x10000000,0xc00000,0x60061,0x400,
-    0x0,0x80010008,0x249248a4,0x8229048,
-    0x2092,0x6c3603,0xb61b6c30,0x6db6036d,
-    0xdb6c0,0x361b0180,0x91b72000,0xdb11b71b,
-    0x6db6236,0x1008200,0x12480012,0x24924906,
-    0x48200049,0x80410002,0x24000900,0x4924a489,
-    0x10822492,0x20800125,0x48360,0x9241b692,
-    0x6da4924,0x40009268,0x241b010,0x291b4900,
-    0x6d249249,0x49493423,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,0x49249249,
-    0x92492492,0x24924924,0x49249249,0x92492492,
-    0x24924924,0x49249249,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,0x49249249,
-    0x92492492,0x24924924,0x49249249,0x92492492,
-    0x24924924,0x49249249,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,0x49249249,
-    0x92492492,0x24924924,0x49249249,0x2492,
-]);
-
-Lev2TParametricDescription.prototype.toStates5 = /*6 bits per value */ new Int32Array([
-    0x801c0144,0x1453803,0x14700038,0xc0005145,
-    0x1401,0x14,0x140000,0x0,
-    0x510000,0x4e00e007,0xe0051,0x3451451c,
-    0xd015000,0x30cd0000,0xc30c30c,0xc30c30d4,
-    0x40c30c30,0x7c01c014,0xc03458c0,0x185e0c07,
-    0x2830c286,0x830c3083,0xc30030,0x33430c,
-    0x30c3003,0x70051030,0x16301f00,0x8301f00d,
-    0x30a18617,0xc20ca0c,0x431420c3,0xb1450c51,
-    0x14314315,0x4f143145,0x34c05401,0x4c30944c,
-    0x55150c3,0x30830055,0x1430c014,0xc00050c3,
-    0xc30850,0xc314300,0x150053c5,0x25130d30,
-    0x5430d30c,0xc0354154,0x300d0c90,0x1cb2cd0c,
-    0xc91cb0c3,0x72c30cb2,0x14f1cb2c,0xc34c0540,
-    0x34c30944,0x82182214,0x851050c2,0x50851430,
-    0x1400c50c,0x30c5085,0x50c51450,0x150053c,
-    0xc25130d3,0x8850d30,0x1430a086,0x450c2144,
-    0x51cb1c21,0x1c91c70c,0xc71c314b,0x34c1cb1,
-    0x6c328328,0xc328014d,0x76cd34a0,0x1401c50c,
-    0xc31c3140,0x31430c30,0x14,0x30c3005,
-    0xa0ca00d3,0x535b0c,0x4d2830ca,0x514369b3,
-    0xc500d01,0x5965965a,0x30d46546,0x6435030c,
-    0x8034c659,0xdb439032,0x2c390034,0xcaaecb24,
-    0x30832872,0xcb28b1c,0x4b1c32cb,0x70030033,
-    0x30b0cb0c,0xe40ca00d,0x400d36d0,0xb2c90b0e,
-    0xca1cb2ab,0xa2c70c20,0x6575d95c,0x4315b5ce,
-    0x95c53831,0x28034c5d,0x9b705583,0xa1455830,
-    0xc76cd6c,0x40143085,0x71451c51,0x871430c,
-    0x450000c3,0xd3071451,0x1560ca00,0x560c26dc,
-    0xb35b2851,0xc914369,0x1a14500d,0x46593945,
-    0xcb2c939,0x94507503,0x328034c3,0x9b70558,
-    0xe41c5583,0x72caaeca,0x1c308510,0xc7147287,
-    0x50871c32,0x1470030c,0xd307147,0xc1560ca0,
-    0x1560c26d,0xabb2b907,0x21441cb2,0x38a1c70c,
-    0x8e657394,0x314b1c93,0x39438738,0x43083081,
-    0x31c22432,0x820c510,0x830d7082,0x50c35c33,
-    0xc30c338,0xc31c30c3,0x50c30c30,0xc204514,
-    0x890c90c2,0x31440c70,0xa8208208,0xea0df0c3,
-    0x8a231430,0xa28a28a2,0x28a28a1e,0x1861868a,
-    0x48308308,0xc3682483,0x14516453,0x4d965845,
-    0xd4659619,0x36590d94,0xd969964,0x546590d9,
-    0x20c20541,0x920d20c,0x5914f0da,0x96114514,
-    0x65865365,0xe89d3519,0x99e7a279,0x9e89e89e,
-    0x81821827,0xb2032430,0x18b28920,0x422492c7,
-    0xb28b0d72,0x3872c35c,0xc30d72cb,0x32cb2972,
-    0x1c75cb0c,0xc90c204e,0xa2482c80,0x24b1c62c,
-    0xc3a89089,0xb0ea2e42,0x9669a31c,0xa4966a28,
-    0x59a8a269,0x8175e7a,0xb203243,0x718b2892,
-    0x4114105c,0x17597658,0x74ce5d96,0x5c36572d,
-    0xd92d7297,0xe1ce5d70,0xc90c204,0xca2482c8,
-    0x4171c62,0x5d961045,0x976585d6,0x79669533,
-    0x964965a2,0x659689e6,0x308175e7,0x24510510,
-    0x451031c2,0xe2420841,0x5c338714,0x453851c3,
-    0x51c51451,0xc30c31c,0x451450c7,0x41440c20,
-    0xc708914,0x82105144,0xf1c58c90,0x1470ea0d,
-    0x61861863,0x8a1e85e8,0x8687a8a2,0x3081861,
-    0x24853c51,0x5053c368,0x1341144f,0x96194ce5,
-    0x1544d439,0x94385514,0xe0d90d96,0x5415464,
-    0x4f1440c2,0xf0da0921,0x4513d414,0x533944d0,
-    0x350e6586,0x86082181,0xe89e981d,0x18277689,
-    0x10308182,0x89207185,0x41c718b2,0x14e24224,
-    0xc35cb287,0xe1c73871,0x28e1c514,0xc70c32cb,
-    0x204e1c75,0x1c61440c,0xc62ca248,0x90891071,
-    0x2e41c58c,0xa31c70ea,0xe86175e7,0xa269a475,
-    0x5e7a57a8,0x51030817,0x28920718,0xf38718b,
-    0xe5134114,0x39961758,0xe1ce4ce,0x728e3855,
-    0x5ce0d92d,0xc204e1ce,0x81c61440,0x1c62ca24,
-    0xd04503ce,0x85d63944,0x75338e65,0x5d86075e,
-    0x89e69647,0x75e76576,
-]);
-Lev2TParametricDescription.prototype.offsetIncrs5 = /*3 bits per value */ new Int32Array([
-    0x10000000,0xc00000,0x60061,0x400,
-    0x0,0x60000008,0x6b003080,0xdb6ab6db,
-    0x2db6,0x800400,0x49245240,0x11482412,
-    0x104904,0x40020000,0x92292000,0xa4b25924,
-    0x9649658,0xd80c000,0xdb0c001b,0x80db6d86,
-    0x6db01b6d,0xc0600003,0x86000d86,0x6db6c36d,
-    0xddadb6ed,0x300001b6,0x6c360,0xe37236e4,
-    0x46db6236,0xdb6c,0x361b018,0xb91b7200,
-    0x6dbb1b71,0x6db763,0x20100820,0x61248001,
-    0x92492490,0x24820004,0x8041000,0x92400090,
-    0x24924830,0x555b6a49,0x2080012,0x20004804,
-    0x49252449,0x84112492,0x4000928,0x240201,
-    0x92922490,0x58924924,0x49456,0x120d8082,
-    0x6da4800,0x69249249,0x249a01b,0x6c04100,
-    0x6d240009,0x92492483,0x24d5adb4,0x60208001,
-    0x92000483,0x24925236,0x6846da49,0x10400092,
-    0x241b0,0x49291b49,0x636d2492,0x92494935,
-    0x24924924,0x49249249,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,0x49249249,
-    0x92492492,0x24924924,0x49249249,0x92492492,
-    0x24924924,0x49249249,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,0x49249249,
-    0x92492492,0x24924924,0x49249249,0x92492492,
-    0x24924924,0x49249249,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,0x49249249,
-    0x92492492,0x24924924,0x49249249,0x92492492,
-    0x24924924,0x49249249,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,0x49249249,
-    0x92492492,0x24924924,0x49249249,0x92492492,
-    0x24924924,0x49249249,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,0x49249249,
-    0x92492492,0x24924924,0x49249249,0x92492492,
-    0x24924924,0x49249249,0x92492492,0x24924924,
-    0x49249249,0x92492492,0x24924924,
-]);
-
-class Lev1TParametricDescription extends ParametricDescription {
-    /**
-     * @param {number} absState
-     * @param {number} position
-     * @param {number} vector
-     * @returns {number}
-    */
-    transition(absState, position, vector) {
-        let state = Math.floor(absState / (this.w + 1));
-        let offset = absState % (this.w + 1);
-
-        if (position === this.w) {
-            if (state < 2) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 2) + state;
-                offset += this.unpack(this.offsetIncrs0, loc, 1);
-                state = this.unpack(this.toStates0, loc, 2) - 1;
-            }
-        } else if (position === this.w - 1) {
-            if (state < 3) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 3) + state;
-                offset += this.unpack(this.offsetIncrs1, loc, 1);
-                state = this.unpack(this.toStates1, loc, 2) - 1;
-            }
-        } else if (position === this.w - 2) {
-            if (state < 6) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 6) + state;
-                offset += this.unpack(this.offsetIncrs2, loc, 2);
-                state = this.unpack(this.toStates2, loc, 3) - 1;
+if (typeof window !== "undefined") {
+    const ROOT_PATH = window.rootPath;
+    /** @type {stringdex.Callbacks|null} */
+    let databaseCallbacks = null;
+    initSearch(window.Stringdex, window.RoaringBitmap, {
+        loadRoot: callbacks => {
+            for (const key in callbacks) {
+                if (Object.hasOwn(callbacks, key)) {
+                    // @ts-ignore
+                    window[key] = callbacks[key];
+                }
             }
-        } else {
-            if (state < 6) { // eslint-disable-line no-lonely-if
-                const loc = Math.imul(vector, 6) + state;
-                offset += this.unpack(this.offsetIncrs3, loc, 2);
-                state = this.unpack(this.toStates3, loc, 3) - 1;
+            databaseCallbacks = callbacks;
+            // search.index/root is loaded by main.js, so
+            // this script doesn't need to launch it, but
+            // must pick it up
+            if (window.searchIndex) {
+                window.rr_(window.searchIndex);
             }
-        }
-
-        if (state === -1) {
-            // null state
-            return -1;
-        } else {
-            // translate back to abs
-            return Math.imul(state, this.w + 1) + offset;
-        }
-    }
-
-    // state map
-    //   0 -> [(0, 0)]
-    //   1 -> [(0, 1)]
-    //   2 -> [(0, 1), (1, 1)]
-    //   3 -> [(0, 1), (1, 1), (2, 1)]
-    //   4 -> [(0, 1), (2, 1)]
-    //   5 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
-
-
-    /** @param {number} w - length of word being checked */
-    constructor(w) {
-        super(w, 1, new Int32Array([0,1,0,-1,-1,-1]));
-    }
-}
-
-Lev1TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
-    0x2,
-]);
-Lev1TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
-    0x0,
-]);
-
-Lev1TParametricDescription.prototype.toStates1 = /*2 bits per value */ new Int32Array([
-    0xa43,
-]);
-Lev1TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
-    0x38,
-]);
-
-Lev1TParametricDescription.prototype.toStates2 = /*3 bits per value */ new Int32Array([
-    0x12180003,0xb45a4914,0x69,
-]);
-Lev1TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
-    0x558a0000,0x5555,
-]);
-
-Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32Array([
-    0x900c0003,0xa1904864,0x45a49169,0x5a6d196a,
-    0x9634,
-]);
-Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
-    0xa0fc0000,0x5555ba08,0x55555555,
-]);
-
-// ====================
-// WARNING: Nothing should be added below this comment: we need the `initSearch` function to
-// be called ONLY when the whole file has been parsed and loaded.
-
-// @ts-expect-error
-function initSearch(searchIndex) {
-    rawSearchIndex = searchIndex;
-    if (typeof window !== "undefined") {
-        // @ts-expect-error
-        docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
-        registerSearchEvents();
-        // If there's a search term in the URL, execute the search now.
-        if (window.searchState.getQueryStringParams().search) {
-            search();
-        }
-    } else if (typeof exports !== "undefined") {
-        // @ts-expect-error
-        docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
-        exports.docSearch = docSearch;
-        exports.parseQuery = DocSearch.parseQuery;
-    }
-}
-
-if (typeof exports !== "undefined") {
+        },
+        loadTreeByHash: hashHex => {
+            const script = document.createElement("script");
+            script.src = `${ROOT_PATH}search.index/${hashHex}.js`;
+            script.onerror = e => {
+                if (databaseCallbacks) {
+                    databaseCallbacks.err_rn_(hashHex, e);
+                }
+            };
+            document.documentElement.appendChild(script);
+        },
+        loadDataByNameAndHash: (name, hashHex) => {
+            const script = document.createElement("script");
+            script.src = `${ROOT_PATH}search.index/${name}/${hashHex}.js`;
+            script.onerror = e => {
+                if (databaseCallbacks) {
+                    databaseCallbacks.err_rd_(hashHex, e);
+                }
+            };
+            document.documentElement.appendChild(script);
+        },
+    });
+} else if (typeof exports !== "undefined") {
+    // eslint-disable-next-line no-undef
     exports.initSearch = initSearch;
 }
-
-if (typeof window !== "undefined") {
-    // @ts-expect-error
-    window.initSearch = initSearch;
-    // @ts-expect-error
-    if (window.searchIndex !== undefined) {
-        // @ts-expect-error
-        initSearch(window.searchIndex);
-    }
-} else {
-    // Running in Node, not a browser. Run initSearch just to produce the
-    // exports.
-    initSearch(new Map());
-}
diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js
index 2430b5829b2..347d3d0750e 100644
--- a/src/librustdoc/html/static/js/settings.js
+++ b/src/librustdoc/html/static/js/settings.js
@@ -1,7 +1,7 @@
 // Local js definitions:
 /* global getSettingValue, updateLocalStorage, updateTheme */
 /* global addClass, removeClass, onEach, onEachLazy */
-/* global MAIN_ID, getVar, getSettingsButton, getHelpButton, nonnull */
+/* global MAIN_ID, getVar, nonnull */
 
 "use strict";
 
@@ -9,18 +9,6 @@
     const isSettingsPage = window.location.pathname.endsWith("/settings.html");
 
     /**
-     * @param {Element} elem
-     * @param {EventTarget|null} target
-     */
-    function elemContainsTarget(elem, target) {
-        if (target instanceof Node) {
-            return elem.contains(target);
-        } else {
-            return false;
-        }
-    }
-
-    /**
      * @overload {"theme"|"preferred-dark-theme"|"preferred-light-theme"}
      * @param {string} settingName
      * @param {string} value
@@ -305,10 +293,12 @@
             }
         } else {
             el.setAttribute("tabindex", "-1");
-            const settingsBtn = getSettingsButton();
-            if (settingsBtn !== null) {
-                settingsBtn.appendChild(el);
-            }
+            onEachLazy(document.querySelectorAll(".settings-menu"), menu => {
+                if (menu.offsetWidth !== 0) {
+                    menu.appendChild(el);
+                    return true;
+                }
+            });
         }
         return el;
     }
@@ -317,6 +307,15 @@
 
     function displaySettings() {
         settingsMenu.style.display = "";
+        onEachLazy(document.querySelectorAll(".settings-menu"), menu => {
+            if (menu.offsetWidth !== 0) {
+                if (!menu.contains(settingsMenu) && settingsMenu.parentElement) {
+                    settingsMenu.parentElement.removeChild(settingsMenu);
+                    menu.appendChild(settingsMenu);
+                }
+                return true;
+            }
+        });
         onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => {
             const val = getSettingValue(el.id);
             const checked = val === "true";
@@ -330,40 +329,37 @@
      * @param {FocusEvent} event
      */
     function settingsBlurHandler(event) {
-        const helpBtn = getHelpButton();
-        const settingsBtn = getSettingsButton();
-        const helpUnfocused = helpBtn === null ||
-              (!helpBtn.contains(document.activeElement) &&
-               !elemContainsTarget(helpBtn, event.relatedTarget));
-        const settingsUnfocused = settingsBtn === null ||
-              (!settingsBtn.contains(document.activeElement) &&
-               !elemContainsTarget(settingsBtn, event.relatedTarget));
-        if (helpUnfocused && settingsUnfocused) {
+        const isInPopover = onEachLazy(
+            document.querySelectorAll(".settings-menu, .help-menu"),
+            menu => {
+                return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);
+            },
+        );
+        if (!isInPopover) {
             window.hidePopoverMenus();
         }
     }
 
     if (!isSettingsPage) {
         // We replace the existing "onclick" callback.
-        // These elements must exist, as (outside of the settings page)
-        // `settings.js` is only loaded after the settings button is clicked.
-        const settingsButton = nonnull(getSettingsButton());
         const settingsMenu = nonnull(document.getElementById("settings"));
-        settingsButton.onclick = event => {
-            if (elemContainsTarget(settingsMenu, event.target)) {
-                return;
-            }
-            event.preventDefault();
-            const shouldDisplaySettings = settingsMenu.style.display === "none";
+        onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {
+            /** @param {MouseEvent} event */
+            settingsButton.querySelector("a").onclick = event => {
+                if (!(event.target instanceof Element) || settingsMenu.contains(event.target)) {
+                    return;
+                }
+                event.preventDefault();
+                const shouldDisplaySettings = settingsMenu.style.display === "none";
 
-            window.hideAllModals(false);
-            if (shouldDisplaySettings) {
-                displaySettings();
-            }
-        };
-        settingsButton.onblur = settingsBlurHandler;
-        // the settings button should always have a link in it
-        nonnull(settingsButton.querySelector("a")).onblur = settingsBlurHandler;
+                window.hideAllModals(false);
+                if (shouldDisplaySettings) {
+                    displaySettings();
+                }
+            };
+            settingsButton.onblur = settingsBlurHandler;
+            settingsButton.querySelector("a").onblur = settingsBlurHandler;
+        });
         onEachLazy(settingsMenu.querySelectorAll("input"), el => {
             el.onblur = settingsBlurHandler;
         });
@@ -377,6 +373,8 @@
         if (!isSettingsPage) {
             displaySettings();
         }
-        removeClass(getSettingsButton(), "rotate");
+        onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {
+            removeClass(settingsButton, "rotate");
+        });
     }, 0);
 })();
diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js
index ca13b891638..40ab8be03c9 100644
--- a/src/librustdoc/html/static/js/storage.js
+++ b/src/librustdoc/html/static/js/storage.js
@@ -7,6 +7,7 @@
 
 /**
  * @import * as rustdoc from "./rustdoc.d.ts";
+ * @import * as stringdex from "./stringdex.d.ts";
  */
 
 const builtinThemes = ["light", "dark", "ayu"];
@@ -116,7 +117,7 @@ function addClass(elem, className) {
  * Remove a class from a DOM Element. If `elem` is null,
  * does nothing. This function is idempotent.
  *
- * @param {Element|null} elem
+ * @param {Element|null|undefined} elem
  * @param {string} className
  */
 // eslint-disable-next-line no-unused-vars
@@ -172,7 +173,7 @@ function updateLocalStorage(name, value) {
         } else {
             window.localStorage.setItem("rustdoc-" + name, value);
         }
-    } catch (e) {
+    } catch {
         // localStorage is not accessible, do nothing
     }
 }
@@ -189,7 +190,7 @@ function updateLocalStorage(name, value) {
 function getCurrentValue(name) {
     try {
         return window.localStorage.getItem("rustdoc-" + name);
-    } catch (e) {
+    } catch {
         return null;
     }
 }
@@ -375,32 +376,6 @@ window.addEventListener("pageshow", ev => {
 // That's also why this is in storage.js and not main.js.
 //
 // [parser]: https://html.spec.whatwg.org/multipage/parsing.html
-class RustdocSearchElement extends HTMLElement {
-    constructor() {
-        super();
-    }
-    connectedCallback() {
-        const rootPath = getVar("root-path");
-        const currentCrate = getVar("current-crate");
-        this.innerHTML = `<nav class="sub">
-            <form class="search-form">
-                <span></span> <!-- This empty span is a hacky fix for Safari - See #93184 -->
-                <div id="sidebar-button" tabindex="-1">
-                    <a href="${rootPath}${currentCrate}/all.html" title="show sidebar"></a>
-                </div>
-                <input
-                    class="search-input"
-                    name="search"
-                    aria-label="Run search in the documentation"
-                    autocomplete="off"
-                    spellcheck="false"
-                    placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…"
-                    type="search">
-            </form>
-        </nav>`;
-    }
-}
-window.customElements.define("rustdoc-search", RustdocSearchElement);
 class RustdocToolbarElement extends HTMLElement {
     constructor() {
         super();
@@ -411,11 +386,15 @@ class RustdocToolbarElement extends HTMLElement {
             return;
         }
         const rootPath = getVar("root-path");
+        const currentUrl = window.location.href.split("?")[0].split("#")[0];
         this.innerHTML = `
-        <div id="settings-menu" tabindex="-1">
+        <div id="search-button" tabindex="-1">
+            <a href="${currentUrl}?search="><span class="label">Search</span></a>
+        </div>
+        <div class="settings-menu" tabindex="-1">
             <a href="${rootPath}settings.html"><span class="label">Settings</span></a>
         </div>
-        <div id="help-button" tabindex="-1">
+        <div class="help-menu" tabindex="-1">
             <a href="${rootPath}help.html"><span class="label">Help</span></a>
         </div>
         <button id="toggle-all-docs"
@@ -424,3 +403,31 @@ class="label">Summary</span></button>`;
     }
 }
 window.customElements.define("rustdoc-toolbar", RustdocToolbarElement);
+class RustdocTopBarElement extends HTMLElement {
+    constructor() {
+        super();
+    }
+    connectedCallback() {
+        const rootPath = getVar("root-path");
+        const tmplt = document.createElement("template");
+        tmplt.innerHTML = `
+        <slot name="sidebar-menu-toggle"></slot>
+        <slot></slot>
+        <slot name="settings-menu"></slot>
+        <slot name="help-menu"></slot>
+        `;
+        const shadow = this.attachShadow({ mode: "open" });
+        shadow.appendChild(tmplt.content.cloneNode(true));
+        this.innerHTML += `
+        <button class="sidebar-menu-toggle" slot="sidebar-menu-toggle" title="show sidebar">
+        </button>
+        <div class="settings-menu" slot="settings-menu" tabindex="-1">
+            <a href="${rootPath}settings.html"><span class="label">Settings</span></a>
+        </div>
+        <div class="help-menu" slot="help-menu" tabindex="-1">
+            <a href="${rootPath}help.html"><span class="label">Help</span></a>
+        </div>
+        `;
+    }
+}
+window.customElements.define("rustdoc-topbar", RustdocTopBarElement);
diff --git a/src/librustdoc/html/static/js/stringdex.d.ts b/src/librustdoc/html/static/js/stringdex.d.ts
new file mode 100644
index 00000000000..cf9a8b6b564
--- /dev/null
+++ b/src/librustdoc/html/static/js/stringdex.d.ts
@@ -0,0 +1,165 @@
+export = stringdex;
+
+declare namespace stringdex {
+    /**
+     * The client interface to Stringdex.
+     */
+    interface Database {
+        getIndex(colname: string): SearchTree|undefined;
+        getData(colname: string): DataColumn|undefined;
+    }
+    /**
+     * A search index file.
+     */
+    interface SearchTree {
+        trie(): Trie;
+        search(name: Uint8Array|string): Promise<Trie?>;
+        searchLev(name: Uint8Array|string): AsyncGenerator<Trie>;
+    }
+    /**
+     * A compressed node in the search tree.
+     *
+     * This object logically addresses two interleaved trees:
+     * a "prefix tree", and a "suffix tree". If you ask for
+     * generic matches, you get both, but if you ask for one
+     * that excludes suffix-only entries, you'll get prefixes
+     * alone.
+     */
+    interface Trie {
+        matches(): RoaringBitmap;
+        substringMatches(): AsyncGenerator<RoaringBitmap>;
+        prefixMatches(): AsyncGenerator<RoaringBitmap>;
+        keys(): Uint8Array;
+        keysExcludeSuffixOnly(): Uint8Array;
+        children(): [number, Promise<Trie>][];
+        childrenExcludeSuffixOnly(): [number, Promise<Trie>][];
+        child(id: number): Promise<Trie>?;
+    }
+    /**
+     * The client interface to Stringdex.
+     */
+    interface DataColumn {
+        isEmpty(id: number): boolean;
+        at(id: number): Promise<Uint8Array|undefined>;
+        length: number,
+    }
+    /**
+     * Callbacks for a host application and VFS backend.
+     *
+     * These functions are calleb with mostly-raw data,
+     * except the JSONP wrapper is removed. For example,
+     * a file with the contents `rr_('{"A":"B"}')` should,
+     * after being pulled in, result in the `rr_` callback
+     * being invoked.
+     *
+     * The success callbacks don't need to supply the name of
+     * the file that succeeded, but, if you want successful error
+     * reporting, you'll need to remember which files are
+     * in flight and report the filename as the first parameter.
+     */
+    interface Callbacks {
+        /**
+         * Load the root of the search database
+         * @param {string} dataString
+         */
+        rr_: function(string);
+        err_rr_: function(any);
+        /**
+         * Load a nodefile in the search tree.
+         * A node file may contain multiple nodes;
+         * each node has five fields, separated by newlines.
+         * @param {string} inputBase64
+         */
+        rn_: function(string);
+        err_rn_: function(string, any);
+        /**
+         * Load a database column partition from a string
+         * @param {string} dataString
+         */
+        rd_: function(string);
+        err_rd_: function(string, any);
+        /**
+         * Load a database column partition from base64
+         * @param {string} dataString
+         */
+        rb_: function(string);
+        err_rb_: function(string, any);
+    };
+    /**
+     * Hooks that a VFS layer must provide for stringdex to load data.
+     *
+     * When the root is loaded, the Callbacks object is provided. These
+     * functions should result in callback functions being called with
+     * the contents of the file, or in error callbacks being invoked with
+     * the failed-to-load filename.
+     */
+    interface Hooks {
+        /**
+         * The first function invoked as part of loading a search database.
+         * This function must, eventually, invoke `rr_` with the string
+         * representation of the root file (the function call wrapper,
+         * `rr_('` and `')`, must be removed).
+         *
+         * The supplied callbacks object is used to feed search data back
+         * to the search engine core. You have to store it, so that
+         * loadTreeByHash and loadDataByNameAndHash can use it.
+         *
+         * If this fails, either throw an exception, or call `err_rr_`
+         * with the error object.
+         */
+        loadRoot: function(Callbacks);
+        /**
+         * Load a subtree file from the search index.
+         * 
+         * If this function succeeds, call `rn_` on the callbacks
+         * object. If it fails, call `err_rn_(hashHex, error)`.
+         * 
+         * @param {string} hashHex
+         */
+        loadTreeByHash: function(string);
+        /**
+         * Load a column partition from the search database.
+         *
+         * If this function succeeds, call `rd_` or `rb_` on the callbacks
+         * object. If it fails, call `err_rd_(hashHex, error)`. or `err_rb_`.
+         * To determine which one, the wrapping function call in the js file
+         * specifies it.
+         *
+         * @param {string} columnName
+         * @param {string} hashHex
+         */
+        loadDataByNameAndHash: function(string, string);
+    };
+    class RoaringBitmap {
+        constructor(array: Uint8Array|null, start?: number);
+        static makeSingleton(number: number);
+        static everything(): RoaringBitmap;
+        static empty(): RoaringBitmap;
+        isEmpty(): boolean;
+        union(that: RoaringBitmap): RoaringBitmap;
+        intersection(that: RoaringBitmap): RoaringBitmap;
+        contains(number: number): boolean;
+        entries(): Generator<number>;
+        first(): number|null;
+        consumed_len_bytes: number;
+    };
+
+    type Stringdex = {
+        /**
+         * Initialize Stringdex with VFS hooks.
+         * Returns a database that you can use.
+         */
+        loadDatabase: function(Hooks): Promise<Database>,
+    };
+
+    const Stringdex: Stringdex;
+    const RoaringBitmap: Class<stringdex.RoaringBitmap>;
+}
+
+declare global {
+    interface Window {
+        Stringdex: stringdex.Stringdex;
+        RoaringBitmap: Class<stringdex.RoaringBitmap>;
+        StringdexOnload: Array<function(stringdex.Stringdex): any>?;
+    };
+}
\ No newline at end of file
diff --git a/src/librustdoc/html/static/js/stringdex.js b/src/librustdoc/html/static/js/stringdex.js
new file mode 100644
index 00000000000..cb956d926db
--- /dev/null
+++ b/src/librustdoc/html/static/js/stringdex.js
@@ -0,0 +1,3217 @@
+/**
+ * @import * as stringdex from "./stringdex.d.ts"
+ */
+
+const EMPTY_UINT8 = new Uint8Array();
+
+/**
+ * @property {Uint8Array} keysAndCardinalities
+ * @property {Uint8Array[]} containers
+ */
+class RoaringBitmap {
+    /**
+     * @param {Uint8Array|null} u8array
+     * @param {number} [startingOffset]
+    */
+    constructor(u8array, startingOffset) {
+        const start = startingOffset ? startingOffset : 0;
+        let i = start;
+        /** @type {Uint8Array} */
+        this.keysAndCardinalities = EMPTY_UINT8;
+        /** @type {(RoaringBitmapArray|RoaringBitmapBits|RoaringBitmapRun)[]} */
+        this.containers = [];
+        /** @type {number} */
+        this.consumed_len_bytes = 0;
+        if (u8array === null || u8array.length === i || u8array[i] === 0) {
+            return this;
+        } else if (u8array[i] > 0xf0) {
+            // Special representation of tiny sets that are close together
+            const lspecial = u8array[i] & 0x0f;
+            this.keysAndCardinalities = new Uint8Array(lspecial * 4);
+            let pspecial = i + 1;
+            let key = u8array[pspecial + 2] | (u8array[pspecial + 3] << 8);
+            let value = u8array[pspecial] | (u8array[pspecial + 1] << 8);
+            let entry = (key << 16) | value;
+            let container;
+            container = new RoaringBitmapArray(1, new Uint8Array(4));
+            container.array[0] = value & 0xFF;
+            container.array[1] = (value >> 8) & 0xFF;
+            this.containers.push(container);
+            this.keysAndCardinalities[0] = key;
+            this.keysAndCardinalities[1] = key >> 8;
+            pspecial += 4;
+            for (let ispecial = 1; ispecial < lspecial; ispecial += 1) {
+                entry += u8array[pspecial] | (u8array[pspecial + 1] << 8);
+                value = entry & 0xffff;
+                key = entry >> 16;
+                container = this.addToArrayAt(key);
+                const cardinalityOld = container.cardinality;
+                container.array[cardinalityOld * 2] = value & 0xFF;
+                container.array[(cardinalityOld * 2) + 1] = (value >> 8) & 0xFF;
+                container.cardinality = cardinalityOld + 1;
+                pspecial += 2;
+            }
+            this.consumed_len_bytes = pspecial - i;
+            return this;
+        } else if (u8array[i] < 0x3a) {
+            // Special representation of tiny sets with arbitrary 32-bit integers
+            const lspecial = u8array[i];
+            this.keysAndCardinalities = new Uint8Array(lspecial * 4);
+            let pspecial = i + 1;
+            for (let ispecial = 0; ispecial < lspecial; ispecial += 1) {
+                const key = u8array[pspecial + 2] | (u8array[pspecial + 3] << 8);
+                const value = u8array[pspecial] | (u8array[pspecial + 1] << 8);
+                const container = this.addToArrayAt(key);
+                const cardinalityOld = container.cardinality;
+                container.array[cardinalityOld * 2] = value & 0xFF;
+                container.array[(cardinalityOld * 2) + 1] = (value >> 8) & 0xFF;
+                container.cardinality = cardinalityOld + 1;
+                pspecial += 4;
+            }
+            this.consumed_len_bytes = pspecial - i;
+            return this;
+        }
+        // https://github.com/RoaringBitmap/RoaringFormatSpec
+        //
+        // Roaring bitmaps are used for flags that can be kept in their
+        // compressed form, even when loaded into memory. This decoder
+        // turns the containers into objects, but uses byte array
+        // slices of the original format for the data payload.
+        const has_runs = u8array[i] === 0x3b;
+        if (u8array[i] !== 0x3a && u8array[i] !== 0x3b) {
+            throw new Error("not a roaring bitmap: " + u8array[i]);
+        }
+        const size = has_runs ?
+            ((u8array[i + 2] | (u8array[i + 3] << 8)) + 1) :
+            ((u8array[i + 4] | (u8array[i + 5] << 8) |
+             (u8array[i + 6] << 16) | (u8array[i + 7] << 24)));
+        i += has_runs ? 4 : 8;
+        let is_run;
+        if (has_runs) {
+            const is_run_len = (size + 7) >> 3;
+            is_run = new Uint8Array(u8array.buffer, i + u8array.byteOffset, is_run_len);
+            i += is_run_len;
+        } else {
+            is_run = EMPTY_UINT8;
+        }
+        this.keysAndCardinalities = u8array.subarray(i, i + (size * 4));
+        i += size * 4;
+        let offsets = null;
+        if (!has_runs || size >= 4) {
+            offsets = [];
+            for (let j = 0; j < size; ++j) {
+                offsets.push(u8array[i] | (u8array[i + 1] << 8) | (u8array[i + 2] << 16) |
+                    (u8array[i + 3] << 24));
+                i += 4;
+            }
+        }
+        for (let j = 0; j < size; ++j) {
+            if (offsets && offsets[j] !== i - start) {
+                throw new Error(`corrupt bitmap ${j}: ${i - start} / ${offsets[j]}`);
+            }
+            const cardinality = (this.keysAndCardinalities[(j * 4) + 2] |
+                (this.keysAndCardinalities[(j * 4) + 3] << 8)) + 1;
+            if (is_run[j >> 3] & (1 << (j & 0x7))) {
+                const runcount = (u8array[i] | (u8array[i + 1] << 8));
+                i += 2;
+                this.containers.push(new RoaringBitmapRun(
+                    runcount,
+                    new Uint8Array(u8array.buffer, i + u8array.byteOffset, runcount * 4),
+                ));
+                i += runcount * 4;
+            } else if (cardinality >= 4096) {
+                this.containers.push(new RoaringBitmapBits(new Uint8Array(
+                    u8array.buffer,
+                    i + u8array.byteOffset, 8192,
+                )));
+                i += 8192;
+            } else {
+                const end = cardinality * 2;
+                this.containers.push(new RoaringBitmapArray(
+                    cardinality,
+                    new Uint8Array(u8array.buffer, i + u8array.byteOffset, end),
+                ));
+                i += end;
+            }
+        }
+        this.consumed_len_bytes = i - start;
+    }
+    /**
+     * @param {number} number
+     * @returns {RoaringBitmap}
+     */
+    static makeSingleton(number) {
+        const result = new RoaringBitmap(null, 0);
+        result.keysAndCardinalities = Uint8Array.of(
+            (number >> 16), (number >> 24),
+            0, 0, // keysAndCardinalities stores the true cardinality minus 1
+        );
+        result.containers.push(new RoaringBitmapArray(
+            1,
+            Uint8Array.of(number, number >> 8),
+        ));
+        return result;
+    }
+    /** @returns {RoaringBitmap} */
+    static everything() {
+        if (EVERYTHING_BITMAP.isEmpty()) {
+            let i = 0;
+            const l = 1 << 16;
+            const everything_range = new RoaringBitmapRun(1, Uint8Array.of(0, 0, 0xff, 0xff));
+            EVERYTHING_BITMAP.keysAndCardinalities = new Uint8Array(l * 4);
+            while (i < l) {
+                EVERYTHING_BITMAP.containers.push(everything_range);
+                // key
+                EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 0] = i;
+                EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 1] = i >> 8;
+                // cardinality (minus one)
+                EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 2] = 0xff;
+                EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 3] = 0xff;
+                i += 1;
+            }
+        }
+        return EVERYTHING_BITMAP;
+    }
+    /** @returns {RoaringBitmap} */
+    static empty() {
+        return EMPTY_BITMAP;
+    }
+    /** @returns {boolean} */
+    isEmpty() {
+        return this.containers.length === 0;
+    }
+    /**
+     * Helper function used when constructing bitmaps from lists.
+     * Returns an array container with at least two free byte slots
+     * and bumps `this.cardinalities`.
+     * @param {number} key
+     * @returns {RoaringBitmapArray}
+     */
+    addToArrayAt(key) {
+        let mid = this.getContainerId(key);
+        /** @type {RoaringBitmapArray|RoaringBitmapBits|RoaringBitmapRun} */
+        let container;
+        if (mid === -1) {
+            container = new RoaringBitmapArray(0, new Uint8Array(2));
+            mid = this.containers.length;
+            this.containers.push(container);
+            if (mid * 4 > this.keysAndCardinalities.length) {
+                const keysAndContainers = new Uint8Array(mid * 8);
+                keysAndContainers.set(this.keysAndCardinalities);
+                this.keysAndCardinalities = keysAndContainers;
+            }
+            this.keysAndCardinalities[(mid * 4) + 0] = key;
+            this.keysAndCardinalities[(mid * 4) + 1] = key >> 8;
+        } else {
+            container = this.containers[mid];
+            const cardinalityOld =
+                this.keysAndCardinalities[(mid * 4) + 2] |
+                (this.keysAndCardinalities[(mid * 4) + 3] << 8);
+            const cardinality = cardinalityOld + 1;
+            this.keysAndCardinalities[(mid * 4) + 2] = cardinality;
+            this.keysAndCardinalities[(mid * 4) + 3] = cardinality >> 8;
+        }
+        // the logic for handing this number is annoying, because keysAndCardinalities stores
+        // the cardinality *minus one*, so that it can count up to 65536 with only two bytes
+        // (because empty containers are never stored).
+        //
+        // So, if this is a new container, the stored cardinality contains `0 0`, which is
+        // the proper value of the old cardinality (an imaginary empty container existed).
+        // If this is adding to an existing container, then the above `else` branch bumps it
+        // by one, leaving us with a proper value of `cardinality - 1`.
+        const cardinalityOld =
+            this.keysAndCardinalities[(mid * 4) + 2] |
+            (this.keysAndCardinalities[(mid * 4) + 3] << 8);
+        if (!(container instanceof RoaringBitmapArray) ||
+            container.array.byteLength < ((cardinalityOld + 1) * 2)
+        ) {
+            const newBuf = new Uint8Array((cardinalityOld + 1) * 4);
+            let idx = 0;
+            for (const cvalue of container.values()) {
+                newBuf[idx] = cvalue & 0xFF;
+                newBuf[idx + 1] = (cvalue >> 8) & 0xFF;
+                idx += 2;
+            }
+            if (container instanceof RoaringBitmapArray) {
+                container.cardinality = cardinalityOld;
+                container.array = newBuf;
+                return container;
+            }
+            const newcontainer = new RoaringBitmapArray(cardinalityOld, newBuf);
+            this.containers[mid] = newcontainer;
+            return newcontainer;
+        } else {
+            return container;
+        }
+    }
+    /**
+     * @param {RoaringBitmap} that
+     * @returns {RoaringBitmap}
+     */
+    union(that) {
+        if (this.isEmpty()) {
+            return that;
+        }
+        if (that.isEmpty()) {
+            return this;
+        }
+        if (this === RoaringBitmap.everything() || that === RoaringBitmap.everything()) {
+            return RoaringBitmap.everything();
+        }
+        let i = 0;
+        const il = this.containers.length;
+        let j = 0;
+        const jl = that.containers.length;
+        const result = new RoaringBitmap(null, 0);
+        result.keysAndCardinalities = new Uint8Array((il + jl) * 4);
+        while (i < il || j < jl) {
+            const ik = i * 4;
+            const jk = j * 4;
+            const k = result.containers.length * 4;
+            if (j >= jl || (i < il && (
+                (this.keysAndCardinalities[ik + 1] < that.keysAndCardinalities[jk + 1]) ||
+                (this.keysAndCardinalities[ik + 1] === that.keysAndCardinalities[jk + 1] &&
+                    this.keysAndCardinalities[ik] < that.keysAndCardinalities[jk])
+            ))) {
+                result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0];
+                result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1];
+                result.keysAndCardinalities[k + 2] = this.keysAndCardinalities[ik + 2];
+                result.keysAndCardinalities[k + 3] = this.keysAndCardinalities[ik + 3];
+                result.containers.push(this.containers[i]);
+                i += 1;
+            } else if (i >= il || (j < jl && (
+                (that.keysAndCardinalities[jk + 1] < this.keysAndCardinalities[ik + 1]) ||
+                (that.keysAndCardinalities[jk + 1] === this.keysAndCardinalities[ik + 1] &&
+                    that.keysAndCardinalities[jk] < this.keysAndCardinalities[ik])
+            ))) {
+                result.keysAndCardinalities[k + 0] = that.keysAndCardinalities[jk + 0];
+                result.keysAndCardinalities[k + 1] = that.keysAndCardinalities[jk + 1];
+                result.keysAndCardinalities[k + 2] = that.keysAndCardinalities[jk + 2];
+                result.keysAndCardinalities[k + 3] = that.keysAndCardinalities[jk + 3];
+                result.containers.push(that.containers[j]);
+                j += 1;
+            } else {
+                // this key is not smaller than that key
+                // that key is not smaller than this key
+                // they must be equal
+                const thisContainer = this.containers[i];
+                const thatContainer = that.containers[j];
+                let card = 0;
+                if (thisContainer instanceof RoaringBitmapBits &&
+                    thatContainer instanceof RoaringBitmapBits
+                ) {
+                    const resultArray = new Uint8Array(
+                        thisContainer.array.length > thatContainer.array.length ?
+                            thisContainer.array.length :
+                            thatContainer.array.length,
+                    );
+                    let k = 0;
+                    const kl = resultArray.length;
+                    while (k < kl) {
+                        const c = thisContainer.array[k] | thatContainer.array[k];
+                        resultArray[k] = c;
+                        card += bitCount(c);
+                        k += 1;
+                    }
+                    result.containers.push(new RoaringBitmapBits(resultArray));
+                } else {
+                    const thisValues = thisContainer.values();
+                    const thatValues = thatContainer.values();
+                    let thisResult = thisValues.next();
+                    let thatResult = thatValues.next();
+                    /** @type {Array<number>} */
+                    const resultValues = [];
+                    while (!thatResult.done || !thisResult.done) {
+                        // generator will definitely implement the iterator protocol correctly
+                        /** @type {number} */
+                        const thisValue = thisResult.value;
+                        /** @type {number} */
+                        const thatValue = thatResult.value;
+                        if (thatResult.done || thisValue < thatValue) {
+                            resultValues.push(thisValue);
+                            thisResult = thisValues.next();
+                        } else if (thisResult.done || thatValue < thisValue) {
+                            resultValues.push(thatValue);
+                            thatResult = thatValues.next();
+                        } else {
+                            // this value is not smaller than that value
+                            // that value is not smaller than this value
+                            // they must be equal
+                            resultValues.push(thisValue);
+                            thisResult = thisValues.next();
+                            thatResult = thatValues.next();
+                        }
+                    }
+                    const resultArray = new Uint8Array(resultValues.length * 2);
+                    let k = 0;
+                    for (const value of resultValues) {
+                        // roaring bitmap is little endian
+                        resultArray[k] = value & 0xFF;
+                        resultArray[k + 1] = (value >> 8) & 0xFF;
+                        k += 2;
+                    }
+                    result.containers.push(new RoaringBitmapArray(
+                        resultValues.length,
+                        resultArray,
+                    ));
+                    card = resultValues.length;
+                }
+                result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0];
+                result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1];
+                card -= 1;
+                result.keysAndCardinalities[k + 2] = card;
+                result.keysAndCardinalities[k + 3] = card >> 8;
+                i += 1;
+                j += 1;
+            }
+        }
+        return result;
+    }
+    /**
+     * @param {RoaringBitmap} that
+     * @returns {RoaringBitmap}
+     */
+    intersection(that) {
+        if (this.isEmpty() || that.isEmpty()) {
+            return EMPTY_BITMAP;
+        }
+        if (this === RoaringBitmap.everything()) {
+            return that;
+        }
+        if (that === RoaringBitmap.everything()) {
+            return this;
+        }
+        let i = 0;
+        const il = this.containers.length;
+        let j = 0;
+        const jl = that.containers.length;
+        const result = new RoaringBitmap(null, 0);
+        result.keysAndCardinalities = new Uint8Array((il > jl ? il : jl) * 4);
+        while (i < il && j < jl) {
+            const ik = i * 4;
+            const jk = j * 4;
+            const k = result.containers.length * 4;
+            if (j >= jl || (i < il && (
+                (this.keysAndCardinalities[ik + 1] < that.keysAndCardinalities[jk + 1]) ||
+                (this.keysAndCardinalities[ik + 1] === that.keysAndCardinalities[jk + 1] &&
+                    this.keysAndCardinalities[ik] < that.keysAndCardinalities[jk])
+            ))) {
+                i += 1;
+            } else if (i >= il || (j < jl && (
+                (that.keysAndCardinalities[jk + 1] < this.keysAndCardinalities[ik + 1]) ||
+                (that.keysAndCardinalities[jk + 1] === this.keysAndCardinalities[ik + 1] &&
+                    that.keysAndCardinalities[jk] < this.keysAndCardinalities[ik])
+            ))) {
+                j += 1;
+            } else {
+                // this key is not smaller than that key
+                // that key is not smaller than this key
+                // they must be equal
+                const thisContainer = this.containers[i];
+                const thatContainer = that.containers[j];
+                let card = 0;
+                if (thisContainer instanceof RoaringBitmapBits &&
+                    thatContainer instanceof RoaringBitmapBits
+                ) {
+                    const resultArray = new Uint8Array(
+                        thisContainer.array.length > thatContainer.array.length ?
+                            thisContainer.array.length :
+                            thatContainer.array.length,
+                    );
+                    let k = 0;
+                    const kl = resultArray.length;
+                    while (k < kl) {
+                        const c = thisContainer.array[k] & thatContainer.array[k];
+                        resultArray[k] = c;
+                        card += bitCount(c);
+                        k += 1;
+                    }
+                    if (card !== 0) {
+                        result.containers.push(new RoaringBitmapBits(resultArray));
+                    }
+                } else {
+                    const thisValues = thisContainer.values();
+                    const thatValues = thatContainer.values();
+                    let thisValue = thisValues.next();
+                    let thatValue = thatValues.next();
+                    const resultValues = [];
+                    while (!thatValue.done && !thisValue.done) {
+                        if (thisValue.value < thatValue.value) {
+                            thisValue = thisValues.next();
+                        } else if (thatValue.value < thisValue.value) {
+                            thatValue = thatValues.next();
+                        } else {
+                            // this value is not smaller than that value
+                            // that value is not smaller than this value
+                            // they must be equal
+                            resultValues.push(thisValue.value);
+                            thisValue = thisValues.next();
+                            thatValue = thatValues.next();
+                        }
+                    }
+                    card = resultValues.length;
+                    if (card !== 0) {
+                        const resultArray = new Uint8Array(resultValues.length * 2);
+                        let k = 0;
+                        for (const value of resultValues) {
+                            // roaring bitmap is little endian
+                            resultArray[k] = value & 0xFF;
+                            resultArray[k + 1] = (value >> 8) & 0xFF;
+                            k += 2;
+                        }
+                        result.containers.push(new RoaringBitmapArray(
+                            resultValues.length,
+                            resultArray,
+                        ));
+                    }
+                }
+                if (card !== 0) {
+                    result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0];
+                    result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1];
+                    card -= 1;
+                    result.keysAndCardinalities[k + 2] = card;
+                    result.keysAndCardinalities[k + 3] = card >> 8;
+                }
+                i += 1;
+                j += 1;
+            }
+        }
+        return result;
+    }
+    /** @param {number} keyvalue */
+    contains(keyvalue) {
+        const key = keyvalue >> 16;
+        const value = keyvalue & 0xFFFF;
+        const mid = this.getContainerId(key);
+        return mid === -1 ? false : this.containers[mid].contains(value);
+    }
+    /**
+     * @param {number} key
+     * @returns {number}
+     */
+    getContainerId(key) {
+        // Binary search algorithm copied from
+        // https://en.wikipedia.org/wiki/Binary_search#Procedure
+        //
+        // Format is required by specification to be sorted.
+        // Because keys are 16 bits and unique, length can't be
+        // bigger than 2**16, and because we have 32 bits of safe int,
+        // left + right can't overflow.
+        let left = 0;
+        let right = this.containers.length - 1;
+        while (left <= right) {
+            const mid = Math.floor((left + right) / 2);
+            const x = this.keysAndCardinalities[(mid * 4)] |
+                (this.keysAndCardinalities[(mid * 4) + 1] << 8);
+            if (x < key) {
+                left = mid + 1;
+            } else if (x > key) {
+                right = mid - 1;
+            } else {
+                return mid;
+            }
+        }
+        return -1;
+    }
+    * entries() {
+        const l = this.containers.length;
+        for (let i = 0; i < l; ++i) {
+            const key = this.keysAndCardinalities[i * 4] |
+                (this.keysAndCardinalities[(i * 4) + 1] << 8);
+            for (const value of this.containers[i].values()) {
+                yield (key << 16) | value;
+            }
+        }
+    }
+    /**
+     * @returns {number|null}
+     */
+    first() {
+        for (const entry of this.entries()) {
+            return entry;
+        }
+        return null;
+    }
+    /**
+     * @returns {number}
+     */
+    cardinality() {
+        let result = 0;
+        const l = this.containers.length;
+        for (let i = 0; i < l; ++i) {
+            const card = this.keysAndCardinalities[(i * 4) + 2] |
+                (this.keysAndCardinalities[(i * 4) + 3] << 8);
+            result += card + 1;
+        }
+        return result;
+    }
+}
+
+class RoaringBitmapRun {
+    /**
+     * @param {number} runcount
+     * @param {Uint8Array} array
+     */
+    constructor(runcount, array) {
+        this.runcount = runcount;
+        this.array = array;
+    }
+    /** @param {number} value */
+    contains(value) {
+        // Binary search algorithm copied from
+        // https://en.wikipedia.org/wiki/Binary_search#Procedure
+        //
+        // Since runcount is stored as 16 bits, left + right
+        // can't overflow.
+        let left = 0;
+        let right = this.runcount - 1;
+        while (left <= right) {
+            const mid = (left + right) >> 1;
+            const i = mid * 4;
+            const start = this.array[i] | (this.array[i + 1] << 8);
+            const lenm1 = this.array[i + 2] | (this.array[i + 3] << 8);
+            if ((start + lenm1) < value) {
+                left = mid + 1;
+            } else if (start > value) {
+                right = mid - 1;
+            } else {
+                return true;
+            }
+        }
+        return false;
+    }
+    * values() {
+        let i = 0;
+        while (i < this.runcount) {
+            const start = this.array[i * 4] | (this.array[(i * 4) + 1] << 8);
+            const lenm1 = this.array[(i * 4) + 2] | (this.array[(i * 4) + 3] << 8);
+            let value = start;
+            let j = 0;
+            while (j <= lenm1) {
+                yield value;
+                value += 1;
+                j += 1;
+            }
+            i += 1;
+        }
+    }
+}
+class RoaringBitmapArray {
+    /**
+     * @param {number} cardinality
+     * @param {Uint8Array} array
+     */
+    constructor(cardinality, array) {
+        this.cardinality = cardinality;
+        this.array = array;
+    }
+    /** @param {number} value */
+    contains(value) {
+        // Binary search algorithm copied from
+        // https://en.wikipedia.org/wiki/Binary_search#Procedure
+        //
+        // Since cardinality can't be higher than 4096, left + right
+        // cannot overflow.
+        let left = 0;
+        let right = this.cardinality - 1;
+        while (left <= right) {
+            const mid = (left + right) >> 1;
+            const i = mid * 2;
+            const x = this.array[i] | (this.array[i + 1] << 8);
+            if (x < value) {
+                left = mid + 1;
+            } else if (x > value) {
+                right = mid - 1;
+            } else {
+                return true;
+            }
+        }
+        return false;
+    }
+    /** @returns {Generator<number>} */
+    * values() {
+        let i = 0;
+        const l = this.cardinality * 2;
+        while (i < l) {
+            yield this.array[i] | (this.array[i + 1] << 8);
+            i += 2;
+        }
+    }
+}
+class RoaringBitmapBits {
+    /**
+     * @param {Uint8Array} array
+     */
+    constructor(array) {
+        this.array = array;
+    }
+    /** @param {number} value */
+    contains(value) {
+        return !!(this.array[value >> 3] & (1 << (value & 7)));
+    }
+    * values() {
+        let i = 0;
+        const l = this.array.length << 3;
+        while (i < l) {
+            if (this.contains(i)) {
+                yield i;
+            }
+            i += 1;
+        }
+    }
+}
+
+const EMPTY_BITMAP = new RoaringBitmap(null, 0);
+EMPTY_BITMAP.consumed_len_bytes = 0;
+const EMPTY_BITMAP1 = new RoaringBitmap(null, 0);
+EMPTY_BITMAP1.consumed_len_bytes = 1;
+const EVERYTHING_BITMAP = new RoaringBitmap(null, 0);
+
+/**
+ * A mapping from six byte nodeids to an arbitrary value.
+ * We don't just use `Map` because that requires double hashing.
+ * @template T
+ * @property {Uint8Array} keys
+ * @property {T[]} values
+ * @property {number} size
+ * @property {number} capacityClass
+ */
+class HashTable {
+    /**
+     * Construct an empty hash table.
+     */
+    constructor() {
+        this.keys = EMPTY_UINT8;
+        /** @type {(T|undefined)[]} */
+        this.values = [];
+        this.size = 0;
+        this.capacityClass = 0;
+    }
+    /**
+     * @returns {Generator<[Uint8Array, T]>}
+     */
+    * entries() {
+        const keys = this.keys;
+        const values = this.values;
+        const l = this.values.length;
+        for (let i = 0; i < l; i += 1) {
+            const value = values[i];
+            if (value !== undefined) {
+                yield [keys.subarray(i * 6, (i + 1) * 6), value];
+            }
+        }
+    }
+    /**
+     * Add a value to the hash table.
+     * @param {Uint8Array} key
+     * @param {T} value
+     */
+    set(key, value) {
+        // 90 % load factor
+        if (this.size * 10 >= this.values.length * 9) {
+            const keys = this.keys;
+            const values = this.values;
+            const l = values.length;
+            this.capacityClass += 1;
+            const capacity = 1 << this.capacityClass;
+            this.keys = new Uint8Array(capacity * 6);
+            this.values = [];
+            for (let i = 0; i < capacity; i += 1) {
+                this.values.push(undefined);
+            }
+            this.size = 0;
+            for (let i = 0; i < l; i += 1) {
+                const oldValue = values[i];
+                if (oldValue !== undefined) {
+                    this.setNoGrow(keys, i * 6, oldValue);
+                }
+            }
+        }
+        this.setNoGrow(key, 0, value);
+    }
+    /**
+     * @param {Uint8Array} key
+     * @param {number} start
+     * @param {T} value
+     */
+    setNoGrow(key, start, value) {
+        const mask = ~(0xffffffff << this.capacityClass);
+        const keys = this.keys;
+        const values = this.values;
+        const l = 1 << this.capacityClass;
+        // because we know that our values are already hashed,
+        // just chop off the lower four bytes
+        let slot = (
+            (key[start + 2] << 24) |
+            (key[start + 3] << 16) |
+            (key[start + 4] << 8) |
+            key[start + 5]
+        ) & mask;
+        for (let distance = 0; distance < l; ) {
+            const j = slot * 6;
+            const otherValue = values[slot];
+            if (otherValue === undefined) {
+                values[slot] = value;
+                const keysStart = slot * 6;
+                keys[keysStart + 0] = key[start + 0];
+                keys[keysStart + 1] = key[start + 1];
+                keys[keysStart + 2] = key[start + 2];
+                keys[keysStart + 3] = key[start + 3];
+                keys[keysStart + 4] = key[start + 4];
+                keys[keysStart + 5] = key[start + 5];
+                this.size += 1;
+                break;
+            } else if (
+                key[start + 0] === keys[j + 0] &&
+                key[start + 1] === keys[j + 1] &&
+                key[start + 2] === keys[j + 2] &&
+                key[start + 3] === keys[j + 3] &&
+                key[start + 4] === keys[j + 4] &&
+                key[start + 5] === keys[j + 5]
+            ) {
+                values[slot] = value;
+                break;
+            } else {
+                const otherPreferredSlot = (
+                    (keys[j + 2] << 24) | (keys[j + 3] << 16) |
+                    (keys[j + 4] << 8) | keys[j + 5]
+                ) & mask;
+                const otherDistance = otherPreferredSlot <= slot ?
+                    slot - otherPreferredSlot :
+                    (l - otherPreferredSlot) + slot;
+                if (distance > otherDistance) {
+                    // if the other key is closer to its preferred slot than this one,
+                    // then insert our node in its place and swap
+                    //
+                    // https://cglab.ca/~abeinges/blah/robinhood-part-1/
+                    const otherKey = keys.slice(j, j + 6);
+                    values[slot] = value;
+                    value = otherValue;
+                    keys[j + 0] = key[start + 0];
+                    keys[j + 1] = key[start + 1];
+                    keys[j + 2] = key[start + 2];
+                    keys[j + 3] = key[start + 3];
+                    keys[j + 4] = key[start + 4];
+                    keys[j + 5] = key[start + 5];
+                    key = otherKey;
+                    start = 0;
+                    distance = otherDistance;
+                }
+                distance += 1;
+                slot = (slot + 1) & mask;
+            }
+        }
+    }
+    /**
+     * Retrieve a value
+     * @param {Uint8Array} key
+     * @returns {T|undefined}
+     */
+    get(key) {
+        if (key.length !== 6) {
+            throw "invalid key";
+        }
+        return this.getWithOffsetKey(key, 0);
+    }
+    /**
+     * Retrieve a value
+     * @param {Uint8Array} key
+     * @param {number} start
+     * @returns {T|undefined}
+     */
+    getWithOffsetKey(key, start) {
+        const mask = ~(0xffffffff << this.capacityClass);
+        const keys = this.keys;
+        const values = this.values;
+        const l = 1 << this.capacityClass;
+        // because we know that our values are already hashed,
+        // just chop off the lower four bytes
+        let slot = (
+            (key[start + 2] << 24) |
+            (key[start + 3] << 16) |
+            (key[start + 4] << 8) |
+            key[start + 5]
+        ) & mask;
+        for (let distance = 0; distance < l; distance += 1) {
+            const j = slot * 6;
+            const value = values[slot];
+            if (value === undefined) {
+                break;
+            } else if (
+                key[start + 0] === keys[j + 0] &&
+                key[start + 1] === keys[j + 1] &&
+                key[start + 2] === keys[j + 2] &&
+                key[start + 3] === keys[j + 3] &&
+                key[start + 4] === keys[j + 4] &&
+                key[start + 5] === keys[j + 5]
+            ) {
+                return value;
+            } else {
+                const otherPreferredSlot = (
+                    (keys[j + 2] << 24) | (keys[j + 3] << 16) |
+                    (keys[j + 4] << 8) | keys[j + 5]
+                ) & mask;
+                const otherDistance = otherPreferredSlot <= slot ?
+                    slot - otherPreferredSlot :
+                    (l - otherPreferredSlot) + slot;
+                if (distance > otherDistance) {
+                    break;
+                }
+            }
+            slot = (slot + 1) & mask;
+        }
+        return undefined;
+    }
+}
+
+/*eslint-disable */
+// ignore-tidy-linelength
+/** <https://stackoverflow.com/questions/43122082/efficiently-count-the-number-of-bits-in-an-integer-in-javascript>
+ * @param {number} n
+ * @returns {number}
+ */
+function bitCount(n) {
+    n = (~~n) - ((n >> 1) & 0x55555555);
+    n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
+    return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
+}
+/*eslint-enable */
+
+/**
+ * @param {stringdex.Hooks} hooks
+ * @returns {Promise<stringdex.Database>}
+ */
+function loadDatabase(hooks) {
+    /** @type {stringdex.Callbacks} */
+    const callbacks = {
+        rr_: function(data) {
+            const dataObj = JSON.parse(data);
+            for (const colName of Object.keys(dataObj)) {
+                if (Object.hasOwn(dataObj[colName], "I")) {
+                    registry.searchTreeRoots.set(
+                        colName,
+                        makeSearchTreeFromBase64(dataObj[colName].I)[1],
+                    );
+                }
+                if (Object.hasOwn(dataObj[colName], "N")) {
+                    const counts = [];
+                    const countsstring = dataObj[colName]["N"];
+                    let i = 0;
+                    const l = countsstring.length;
+                    while (i < l) {
+                        let n = 0;
+                        let c = countsstring.charCodeAt(i);
+                        while (c < 96) { // 96 = "`"
+                            n = (n << 4) | (c & 0xF);
+                            i += 1;
+                            c = countsstring.charCodeAt(i);
+                        }
+                        n = (n << 4) | (c & 0xF);
+                        counts.push(n);
+                        i += 1;
+                    }
+                    registry.dataColumns.set(colName, new DataColumn(
+                        counts,
+                        makeUint8ArrayFromBase64(dataObj[colName]["H"]),
+                        new RoaringBitmap(makeUint8ArrayFromBase64(dataObj[colName]["E"]), 0),
+                        colName,
+                    ));
+                }
+            }
+            const cb = registry.searchTreeRootCallback;
+            if (cb) {
+                cb(null, new Database(registry.searchTreeRoots, registry.dataColumns));
+            }
+        },
+        err_rr_: function(err) {
+            const cb = registry.searchTreeRootCallback;
+            if (cb) {
+                cb(err, null);
+            }
+        },
+        rd_: function(dataString) {
+            const l = dataString.length;
+            const data = new Uint8Array(l);
+            for (let i = 0; i < l; ++i) {
+                data[i] = dataString.charCodeAt(i);
+            }
+            loadColumnFromBytes(data);
+        },
+        err_rd_: function(filename, err) {
+            const nodeid = makeUint8ArrayFromHex(filename);
+            const cb = registry.dataColumnLoadPromiseCallbacks.get(nodeid);
+            if (cb) {
+                cb(err, null);
+            }
+        },
+        rb_: function(dataString64) {
+            loadColumnFromBytes(makeUint8ArrayFromBase64(dataString64));
+        },
+        err_rb_: function(filename, err) {
+            const nodeid = makeUint8ArrayFromHex(filename);
+            const cb = registry.dataColumnLoadPromiseCallbacks.get(nodeid);
+            if (cb) {
+                cb(err, null);
+            }
+        },
+        rn_: function(inputBase64) {
+            const [nodeid, tree] = makeSearchTreeFromBase64(inputBase64);
+            const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid);
+            if (cb) {
+                cb(null, tree);
+                registry.searchTreeLoadPromiseCallbacks.set(nodeid, null);
+            }
+        },
+        err_rn_: function(filename, err) {
+            const nodeid = makeUint8ArrayFromHex(filename);
+            const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid);
+            if (cb) {
+                cb(err, null);
+            }
+        },
+    };
+
+    /**
+     * @type {{
+     *      searchTreeRoots: Map<string, SearchTree>;
+     *      searchTreeLoadPromiseCallbacks: HashTable<(function(any, SearchTree?): any)|null>;
+     *      searchTreePromises: HashTable<Promise<SearchTree>>;
+     *      dataColumnLoadPromiseCallbacks: HashTable<function(any, Uint8Array[]?): any>;
+     *      dataColumns: Map<string, DataColumn>;
+     *      dataColumnsBuckets: Map<string, HashTable<Promise<Uint8Array[]>>>;
+     *      searchTreeLoadByNodeID: function(Uint8Array): Promise<SearchTree>;
+     *      searchTreeRootCallback?: function(any, Database?): any;
+     *      dataLoadByNameAndHash: function(string, Uint8Array): Promise<Uint8Array[]>;
+     * }}
+     */
+    const registry = {
+        searchTreeRoots: new Map(),
+        searchTreeLoadPromiseCallbacks: new HashTable(),
+        searchTreePromises: new HashTable(),
+        dataColumnLoadPromiseCallbacks: new HashTable(),
+        dataColumns: new Map(),
+        dataColumnsBuckets: new Map(),
+        searchTreeLoadByNodeID: function(nodeid) {
+            const existingPromise = registry.searchTreePromises.get(nodeid);
+            if (existingPromise) {
+                return existingPromise;
+            }
+            /** @type {Promise<SearchTree>} */
+            let newPromise;
+            if ((nodeid[0] & 0x80) !== 0) {
+                const isWhole = (nodeid[0] & 0x40) !== 0;
+                let leaves;
+                if ((nodeid[0] & 0x10) !== 0) {
+                    let id1 = (nodeid[2] << 8) | nodeid[3];
+                    if ((nodeid[0] & 0x20) !== 0) {
+                        // when data is present, id1 can be up to 20 bits
+                        id1 |= ((nodeid[1] & 0x0f) << 16);
+                    } else {
+                        // otherwise, we fit in 28
+                        id1 |= ((nodeid[0] & 0x0f) << 24) | (nodeid[1] << 16);
+                    }
+                    const id2 = id1 + ((nodeid[4] << 8) | nodeid[5]);
+                    leaves = RoaringBitmap.makeSingleton(id1)
+                        .union(RoaringBitmap.makeSingleton(id2));
+                } else {
+                    leaves = RoaringBitmap.makeSingleton(
+                        (nodeid[2] << 24) | (nodeid[3] << 16) |
+                        (nodeid[4] << 8) | nodeid[5],
+                    );
+                }
+                const data = (nodeid[0] & 0x20) !== 0 ?
+                    Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) :
+                    EMPTY_UINT8;
+                newPromise = Promise.resolve(new SearchTree(
+                    EMPTY_SEARCH_TREE_BRANCHES,
+                    EMPTY_SEARCH_TREE_BRANCHES,
+                    data,
+                    isWhole ? leaves : EMPTY_BITMAP,
+                    isWhole ? EMPTY_BITMAP : leaves,
+                ));
+            } else {
+                const hashHex = makeHexFromUint8Array(nodeid);
+                newPromise = new Promise((resolve, reject) => {
+                    const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid);
+                    if (cb) {
+                        registry.searchTreeLoadPromiseCallbacks.set(nodeid, (err, data) => {
+                            cb(err, data);
+                            if (data) {
+                                resolve(data);
+                            } else {
+                                reject(err);
+                            }
+                        });
+                    } else {
+                        registry.searchTreeLoadPromiseCallbacks.set(nodeid, (err, data) => {
+                            if (data) {
+                                resolve(data);
+                            } else {
+                                reject(err);
+                            }
+                        });
+                        hooks.loadTreeByHash(hashHex);
+                    }
+                });
+            }
+            registry.searchTreePromises.set(nodeid, newPromise);
+            return newPromise;
+        },
+        dataLoadByNameAndHash: function(name, hash) {
+            let dataColumnBuckets = registry.dataColumnsBuckets.get(name);
+            if (dataColumnBuckets === undefined) {
+                dataColumnBuckets = new HashTable();
+                registry.dataColumnsBuckets.set(name, dataColumnBuckets);
+            }
+            const existingBucket = dataColumnBuckets.get(hash);
+            if (existingBucket) {
+                return existingBucket;
+            }
+            const hashHex = makeHexFromUint8Array(hash);
+            /** @type {Promise<Uint8Array[]>} */
+            const newBucket = new Promise((resolve, reject) => {
+                const cb = registry.dataColumnLoadPromiseCallbacks.get(hash);
+                if (cb) {
+                    registry.dataColumnLoadPromiseCallbacks.set(hash, (err, data) => {
+                        cb(err, data);
+                        if (data) {
+                            resolve(data);
+                        } else {
+                            reject(err);
+                        }
+                    });
+                } else {
+                    registry.dataColumnLoadPromiseCallbacks.set(hash, (err, data) => {
+                        if (data) {
+                            resolve(data);
+                        } else {
+                            reject(err);
+                        }
+                    });
+                    hooks.loadDataByNameAndHash(name, hashHex);
+                }
+            });
+            dataColumnBuckets.set(hash, newBucket);
+            return newBucket;
+        },
+    };
+
+    /**
+     * The set of child subtrees.
+     * @type {{
+     *    nodeids: Uint8Array,
+     *    subtrees: Array<Promise<SearchTree>|null>,
+     * }}
+     */
+    class SearchTreeBranches {
+        /**
+         * Construct the subtree list with `length` nulls
+         * @param {number} length
+         * @param {Uint8Array} nodeids
+         */
+        constructor(length, nodeids) {
+            this.nodeids = nodeids;
+            this.subtrees = [];
+            for (let i = 0; i < length; ++i) {
+                this.subtrees.push(null);
+            }
+        }
+        /**
+         * @param {number} i
+         * @returns {Uint8Array}
+        */
+        getNodeID(i) {
+            return new Uint8Array(
+                this.nodeids.buffer,
+                this.nodeids.byteOffset + (i * 6),
+                6,
+            );
+        }
+        // https://github.com/microsoft/TypeScript/issues/17227
+        /** @returns {Generator<[number, Promise<SearchTree>|null]>} */
+        entries() {
+            throw new Error();
+        }
+        /**
+         * @param {number} _k
+         * @returns {number}
+         */
+        getIndex(_k) {
+            throw new Error();
+        }
+        /**
+         * @param {number} _i
+         * @returns {number}
+         */
+        getKey(_i) {
+            throw new Error();
+        }
+        /**
+         * @returns {Uint8Array}
+         */
+        getKeys() {
+            throw new Error();
+        }
+    }
+
+    /**
+     * A sorted array of search tree branches.
+     *
+     * @type {{
+     *    keys: Uint8Array,
+     *    nodeids: Uint8Array,
+     *    subtrees: Array<Promise<SearchTree>|null>,
+     * }}
+     */
+    class SearchTreeBranchesArray extends SearchTreeBranches {
+        /**
+         * @param {Uint8Array} keys
+         * @param {Uint8Array} nodeids
+         */
+        constructor(keys, nodeids) {
+            super(keys.length, nodeids);
+            this.keys = keys;
+            let i = 1;
+            while (i < this.keys.length) {
+                if (this.keys[i - 1] >= this.keys[i]) {
+                    throw new Error("HERE");
+                }
+                i += 1;
+            }
+        }
+        /** @returns {Generator<[number, Promise<SearchTree>|null]>} */
+        * entries() {
+            let i = 0;
+            const l = this.keys.length;
+            while (i < l) {
+                yield [this.keys[i], this.subtrees[i]];
+                i += 1;
+            }
+        }
+        /**
+         * @param {number} k
+         * @returns {number}
+         */
+        getIndex(k) {
+            // Since length can't be bigger than 256,
+            // left + right can't overflow.
+            let left = 0;
+            let right = this.keys.length - 1;
+            while (left <= right) {
+                const mid = (left + right) >> 1;
+                if (this.keys[mid] < k) {
+                    left = mid + 1;
+                } else if (this.keys[mid] > k) {
+                    right = mid - 1;
+                } else {
+                    return mid;
+                }
+            }
+            return -1;
+        }
+        /**
+         * @param {number} i
+         * @returns {number}
+         */
+        getKey(i) {
+            return this.keys[i];
+        }
+        /**
+         * @returns {Uint8Array}
+         */
+        getKeys() {
+            return this.keys;
+        }
+    }
+
+    const EMPTY_SEARCH_TREE_BRANCHES = new SearchTreeBranchesArray(
+        EMPTY_UINT8,
+        EMPTY_UINT8,
+    );
+
+    /** @type {number[]} */
+    const SHORT_ALPHABITMAP_CHARS = [];
+    for (let i = 0x61; i <= 0x7A; ++i) {
+        if (i === 0x76 || i === 0x71) {
+            // 24 entries, 26 letters, so we skip q and v
+            continue;
+        }
+        SHORT_ALPHABITMAP_CHARS.push(i);
+    }
+
+    /** @type {number[]} */
+    const LONG_ALPHABITMAP_CHARS = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36];
+    for (let i = 0x61; i <= 0x7A; ++i) {
+        LONG_ALPHABITMAP_CHARS.push(i);
+    }
+
+    /**
+     * @param {number[]} alphabitmap_chars
+     * @param {number} width
+     * @return {(typeof SearchTreeBranches)&{"ALPHABITMAP_CHARS": number[], "width": number}}
+     */
+    function makeSearchTreeBranchesAlphaBitmapClass(alphabitmap_chars, width) {
+        const bitwidth = width * 8;
+        const cls = class SearchTreeBranchesAlphaBitmap extends SearchTreeBranches {
+            /**
+             * @param {number} bitmap
+             * @param {Uint8Array} nodeids
+             */
+            constructor(bitmap, nodeids) {
+                super(nodeids.length / 6, nodeids);
+                if (nodeids.length / 6 !== bitCount(bitmap)) {
+                    throw new Error(`mismatch ${bitmap} ${nodeids}`);
+                }
+                this.bitmap = bitmap;
+                this.nodeids = nodeids;
+            }
+            /** @returns {Generator<[number, Promise<SearchTree>|null]>} */
+            * entries() {
+                let i = 0;
+                let j = 0;
+                while (i < bitwidth) {
+                    if (this.bitmap & (1 << i)) {
+                        yield [alphabitmap_chars[i], this.subtrees[j]];
+                        j += 1;
+                    }
+                    i += 1;
+                }
+            }
+            /**
+             * @param {number} k
+             * @returns {number}
+             */
+            getIndex(k) {
+                //return this.getKeys().indexOf(k);
+                const ix = alphabitmap_chars.indexOf(k);
+                if (ix < 0) {
+                    return ix;
+                }
+                const result = bitCount(~(0xffffffff << ix) & this.bitmap);
+                return result >= this.subtrees.length ? -1 : result;
+            }
+            /**
+             * @param {number} branch_index
+             * @returns {number}
+             */
+            getKey(branch_index) {
+                //return this.getKeys()[branch_index];
+                let alpha_index = 0;
+                while (branch_index >= 0) {
+                    if (this.bitmap & (1 << alpha_index)) {
+                        branch_index -= 1;
+                    }
+                    alpha_index += 1;
+                }
+                return alphabitmap_chars[alpha_index];
+            }
+            /**
+             * @returns {Uint8Array}
+             */
+            getKeys() {
+                const length = bitCount(this.bitmap);
+                const result = new Uint8Array(length);
+                let result_index = 0;
+                for (let alpha_index = 0; alpha_index < bitwidth; ++alpha_index) {
+                    if (this.bitmap & (1 << alpha_index)) {
+                        result[result_index] = alphabitmap_chars[alpha_index];
+                        result_index += 1;
+                    }
+                }
+                return result;
+            }
+        };
+        cls.ALPHABITMAP_CHARS = alphabitmap_chars;
+        cls.width = width;
+        return cls;
+    }
+
+    const SearchTreeBranchesShortAlphaBitmap =
+        makeSearchTreeBranchesAlphaBitmapClass(SHORT_ALPHABITMAP_CHARS, 3);
+
+    const SearchTreeBranchesLongAlphaBitmap =
+        makeSearchTreeBranchesAlphaBitmapClass(LONG_ALPHABITMAP_CHARS, 4);
+
+    /**
+     * A [suffix tree], used for name-based search.
+     *
+     * This data structure is used to drive substring matches,
+     * such as matching the query "link" to `LinkedList`,
+     * and Lev-distance matches, such as matching the
+     * query "hahsmap" to `HashMap`.
+     *
+     * [suffix tree]: https://en.wikipedia.org/wiki/Suffix_tree
+     *
+     * branches
+     * : A sorted-array map of subtrees.
+     *
+     * data
+     * : The substring represented by this node. The root node
+     *   is always empty.
+     *
+     * leaves_suffix
+     * : The IDs of every entry that matches. Levenshtein matches
+     *   won't include these.
+     *
+     * leaves_whole
+     * : The IDs of every entry that matches exactly. Levenshtein matches
+     *   will include these.
+     *
+     * @type {{
+     *     might_have_prefix_branches: SearchTreeBranches,
+     *     branches: SearchTreeBranches,
+     *     data: Uint8Array,
+     *     leaves_suffix: RoaringBitmap,
+     *     leaves_whole: RoaringBitmap,
+     * }}
+     */
+    class SearchTree {
+        /**
+         * @param {SearchTreeBranches} branches
+         * @param {SearchTreeBranches} might_have_prefix_branches
+         * @param {Uint8Array} data
+         * @param {RoaringBitmap} leaves_whole
+         * @param {RoaringBitmap} leaves_suffix
+         */
+        constructor(
+            branches,
+            might_have_prefix_branches,
+            data,
+            leaves_whole,
+            leaves_suffix,
+        ) {
+            this.might_have_prefix_branches = might_have_prefix_branches;
+            this.branches = branches;
+            this.data = data;
+            this.leaves_suffix = leaves_suffix;
+            this.leaves_whole = leaves_whole;
+        }
+        /**
+         * Returns the Trie for the root node.
+         *
+         * A Trie pointer refers to a single node in a logical decompressed search tree
+         * (the real search tree is compressed).
+         *
+         * @return {Trie}
+         */
+        trie() {
+            return new Trie(this, 0);
+        }
+
+        /**
+         * Return the trie representing `name`
+         * @param {Uint8Array|string} name
+         * @returns {Promise<Trie?>}
+         */
+        async search(name) {
+            if (typeof name === "string") {
+                const utf8encoder = new TextEncoder();
+                name = utf8encoder.encode(name);
+            }
+            let trie = this.trie();
+            for (const datum of name) {
+                // code point definitely exists
+                const newTrie = trie.child(datum);
+                if (newTrie) {
+                    trie = await newTrie;
+                } else {
+                    return null;
+                }
+            }
+            return trie;
+        }
+
+        /**
+         * @param {Uint8Array|string} name
+         * @returns {AsyncGenerator<Trie>}
+         */
+        async* searchLev(name) {
+            if (typeof name === "string") {
+                const utf8encoder = new TextEncoder();
+                name = utf8encoder.encode(name);
+            }
+            const w = name.length;
+            if (w < 3) {
+                const trie = await this.search(name);
+                if (trie !== null) {
+                    yield trie;
+                }
+                return;
+            }
+            const levParams = w >= 6 ?
+                new Lev2TParametricDescription(w) :
+                new Lev1TParametricDescription(w);
+            /** @type {Array<[Promise<Trie>, number]>} */
+            const stack = [[Promise.resolve(this.trie()), 0]];
+            const n = levParams.n;
+            while (stack.length !== 0) {
+                // It's not empty
+                /** @type {[Promise<Trie>, number]} */
+                //@ts-expect-error
+                const [triePromise, levState] = stack.pop();
+                const trie = await triePromise;
+                for (const byte of trie.keysExcludeSuffixOnly()) {
+                    const levPos = levParams.getPosition(levState);
+                    const vector = levParams.getVector(
+                        name,
+                        byte,
+                        levPos,
+                        Math.min(w, levPos + (2 * n) + 1),
+                    );
+                    const newLevState = levParams.transition(
+                        levState,
+                        levPos,
+                        vector,
+                    );
+                    if (newLevState >= 0) {
+                        const child = trie.child(byte);
+                        if (child) {
+                            stack.push([child, newLevState]);
+                            if (levParams.isAccept(newLevState)) {
+                                yield child;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * A representation of a set of strings in the search index,
+     * as a subset of the entire tree.
+     */
+    class Trie {
+        /**
+         * @param {SearchTree} tree
+         * @param {number} offset
+         */
+        constructor(tree, offset) {
+            this.tree = tree;
+            this.offset = offset;
+        }
+
+        /**
+         * All exact matches for the string represented by this node.
+         * @returns {RoaringBitmap}
+         */
+        matches() {
+            if (this.offset === this.tree.data.length) {
+                return this.tree.leaves_whole;
+            } else {
+                return EMPTY_BITMAP;
+            }
+        }
+
+        /**
+         * All matches for strings that contain the string represented by this node.
+         * @returns {AsyncGenerator<RoaringBitmap>}
+         */
+        async* substringMatches() {
+            /** @type {Promise<SearchTree>[]} */
+            let layer = [Promise.resolve(this.tree)];
+            while (layer.length) {
+                const current_layer = layer;
+                layer = [];
+                for await (const tree of current_layer) {
+                    yield tree.leaves_whole.union(tree.leaves_suffix);
+                }
+                /** @type {HashTable<[number, SearchTree][]>} */
+                const subnodes = new HashTable();
+                for await (const node of current_layer) {
+                    const branches = node.branches;
+                    const l = branches.subtrees.length;
+                    for (let i = 0; i < l; ++i) {
+                        const subtree = branches.subtrees[i];
+                        if (subtree) {
+                            layer.push(subtree);
+                        } else if (subtree === null) {
+                            const byte = branches.getKey(i);
+                            const newnode = branches.getNodeID(i);
+                            if (!newnode) {
+                                throw new Error(`malformed tree; no node for key ${byte}`);
+                            } else {
+                                let subnode_list = subnodes.get(newnode);
+                                if (!subnode_list) {
+                                    subnode_list = [[byte, node]];
+                                    subnodes.set(newnode, subnode_list);
+                                } else {
+                                    subnode_list.push([byte, node]);
+                                }
+                            }
+                        } else {
+                            throw new Error(`malformed tree; index ${i} does not exist`);
+                        }
+                    }
+                }
+                for (const [newnode, subnode_list] of subnodes.entries()) {
+                    const res = registry.searchTreeLoadByNodeID(newnode);
+                    for (const [byte, node] of subnode_list) {
+                        const branches = node.branches;
+                        const might_have_prefix_branches = node.might_have_prefix_branches;
+                        const i = branches.getIndex(byte);
+                        branches.subtrees[i] = res;
+                        const mhpI = might_have_prefix_branches.getIndex(byte);
+                        if (mhpI !== -1) {
+                            might_have_prefix_branches.subtrees[mhpI] = res;
+                        }
+                    }
+                    layer.push(res);
+                }
+            }
+        }
+
+        /**
+         * All matches for strings that start with the string represented by this node.
+         * @returns {AsyncGenerator<RoaringBitmap>}
+         */
+        async* prefixMatches() {
+            /** @type {{node: Promise<SearchTree>, len: number}[]} */
+            let layer = [{node: Promise.resolve(this.tree), len: 0}];
+            // https://en.wikipedia.org/wiki/Heap_(data_structure)#Implementation_using_arrays
+            /** @type {{bitmap: RoaringBitmap, length: number}[]} */
+            const backlog = [];
+            while (layer.length !== 0 || backlog.length !== 0) {
+                const current_layer = layer;
+                layer = [];
+                let minLength = null;
+                // push every entry in the current layer into the backlog,
+                // a min-heap of result entries
+                // we then yield the smallest ones (can't yield bigger ones
+                // if we want to do them in order)
+                for (const {node, len} of current_layer) {
+                    const tree = await node;
+                    const length = len + tree.data.length;
+                    if (minLength === null || length < minLength) {
+                        minLength = length;
+                    }
+                    let backlogSlot = backlog.length;
+                    backlog.push({bitmap: tree.leaves_whole, length});
+                    while (backlogSlot > 0 &&
+                        backlog[backlogSlot].length < backlog[(backlogSlot - 1) >> 1].length
+                    ) {
+                        const parentSlot = (backlogSlot - 1) >> 1;
+                        const parent = backlog[parentSlot];
+                        backlog[parentSlot] = backlog[backlogSlot];
+                        backlog[backlogSlot] = parent;
+                        backlogSlot = parentSlot;
+                    }
+                }
+                // yield nodes in length order, smallest one first
+                // we know that, whatever the smallest item is
+                // every child will be bigger than that
+                while (backlog.length !== 0) {
+                    const backlogEntry = backlog[0];
+                    if (minLength !== null && backlogEntry.length > minLength) {
+                        break;
+                    }
+                    if (!backlogEntry.bitmap.isEmpty()) {
+                        yield backlogEntry.bitmap;
+                    }
+                    backlog[0] = backlog[backlog.length - 1];
+                    backlog.length -= 1;
+                    let backlogSlot = 0;
+                    const backlogLength = backlog.length;
+                    while (backlogSlot < backlogLength) {
+                        const leftSlot = (backlogSlot << 1) + 1;
+                        const rightSlot = (backlogSlot << 1) + 2;
+                        let smallest = backlogSlot;
+                        if (leftSlot < backlogLength &&
+                            backlog[leftSlot].length < backlog[smallest].length
+                        ) {
+                            smallest = leftSlot;
+                        }
+                        if (rightSlot < backlogLength &&
+                            backlog[rightSlot].length < backlog[smallest].length
+                        ) {
+                            smallest = rightSlot;
+                        }
+                        if (smallest === backlogSlot) {
+                            break;
+                        } else {
+                            const tmp = backlog[backlogSlot];
+                            backlog[backlogSlot] = backlog[smallest];
+                            backlog[smallest] = tmp;
+                            backlogSlot = smallest;
+                        }
+                    }
+                }
+                // if we still have more subtrees to walk, then keep going
+                /** @type {HashTable<{byte: number, tree: SearchTree, len: number}[]>} */
+                const subnodes = new HashTable();
+                for await (const {node, len} of current_layer) {
+                    const tree = await node;
+                    const length = len + tree.data.length;
+                    const mhp_branches = tree.might_have_prefix_branches;
+                    const l = mhp_branches.subtrees.length;
+                    for (let i = 0; i < l; ++i) {
+                        const len = length + 1;
+                        const subtree = mhp_branches.subtrees[i];
+                        if (subtree) {
+                            layer.push({node: subtree, len});
+                        } else if (subtree === null) {
+                            const byte = mhp_branches.getKey(i);
+                            const newnode = mhp_branches.getNodeID(i);
+                            if (!newnode) {
+                                throw new Error(`malformed tree; no node for key ${byte}`);
+                            } else {
+                                let subnode_list = subnodes.get(newnode);
+                                if (!subnode_list) {
+                                    subnode_list = [{byte, tree, len}];
+                                    subnodes.set(newnode, subnode_list);
+                                } else {
+                                    subnode_list.push({byte, tree, len});
+                                }
+                            }
+                        } else {
+                            throw new Error(`malformed tree; index ${i} does not exist`);
+                        }
+                    }
+                }
+                for (const [newnode, subnode_list] of subnodes.entries()) {
+                    const res = registry.searchTreeLoadByNodeID(newnode);
+                    let len = Number.MAX_SAFE_INTEGER;
+                    for (const {byte, tree, len: subtreelen} of subnode_list) {
+                        if (subtreelen < len) {
+                            len = subtreelen;
+                        }
+                        const mhp_branches = tree.might_have_prefix_branches;
+                        const i = mhp_branches.getIndex(byte);
+                        mhp_branches.subtrees[i] = res;
+                        const branches = tree.branches;
+                        const bi = branches.getIndex(byte);
+                        branches.subtrees[bi] = res;
+                    }
+                    layer.push({node: res, len});
+                }
+            }
+        }
+
+        /**
+         * Returns all keys that are children of this node.
+         * @returns {Uint8Array}
+         */
+        keys() {
+            const data = this.tree.data;
+            if (this.offset === data.length) {
+                return this.tree.branches.getKeys();
+            } else {
+                return Uint8Array.of(data[this.offset]);
+            }
+        }
+
+        /**
+         * Returns all nodes that are direct children of this node.
+         * @returns {[number, Promise<Trie>][]}
+         */
+        children() {
+            const data = this.tree.data;
+            if (this.offset === data.length) {
+                /** @type {[number, Promise<Trie>][]} */
+                const nodes = [];
+                let i = 0;
+                for (const [k, v] of this.tree.branches.entries()) {
+                    /** @type {Promise<SearchTree>} */
+                    let node;
+                    if (v) {
+                        node = v;
+                    } else {
+                        const newnode = this.tree.branches.getNodeID(i);
+                        if (!newnode) {
+                            throw new Error(`malformed tree; no hash for key ${k}: ${newnode} \
+                                ${this.tree.branches.nodeids} ${this.tree.branches.getKeys()}`);
+                        }
+                        node = registry.searchTreeLoadByNodeID(newnode);
+                        this.tree.branches.subtrees[i] = node;
+                        const mhpI = this.tree.might_have_prefix_branches.getIndex(k);
+                        if (mhpI !== -1) {
+                            this.tree.might_have_prefix_branches.subtrees[mhpI] = node;
+                        }
+                    }
+                    nodes.push([k, node.then(node => node.trie())]);
+                    i += 1;
+                }
+                return nodes;
+            } else {
+                /** @type {number} */
+                const codePoint = data[this.offset];
+                const trie = new Trie(this.tree, this.offset + 1);
+                return [[codePoint, Promise.resolve(trie)]];
+            }
+        }
+
+        /**
+         * Returns all keys that are children of this node.
+         * @returns {Uint8Array}
+         */
+        keysExcludeSuffixOnly() {
+            const data = this.tree.data;
+            if (this.offset === data.length) {
+                return this.tree.might_have_prefix_branches.getKeys();
+            } else {
+                return Uint8Array.of(data[this.offset]);
+            }
+        }
+
+        /**
+         * Returns all nodes that are direct children of this node.
+         * @returns {[number, Promise<Trie>][]}
+         */
+        childrenExcludeSuffixOnly() {
+            const data = this.tree.data;
+            if (this.offset === data.length) {
+                /** @type {[number, Promise<Trie>][]} */
+                const nodes = [];
+                let i = 0;
+                for (const [k, v] of this.tree.might_have_prefix_branches.entries()) {
+                    /** @type {Promise<SearchTree>} */
+                    let node;
+                    if (v) {
+                        node = v;
+                    } else {
+                        const newnode = this.tree.might_have_prefix_branches.getNodeID(i);
+                        if (!newnode) {
+                            throw new Error(`malformed tree; no node for key ${k}`);
+                        }
+                        node = registry.searchTreeLoadByNodeID(newnode);
+                        this.tree.might_have_prefix_branches.subtrees[i] = node;
+                        this.tree.branches.subtrees[this.tree.branches.getIndex(k)] = node;
+                    }
+                    nodes.push([k, node.then(node => node.trie())]);
+                    i += 1;
+                }
+                return nodes;
+            } else {
+                /** @type {number} */
+                const codePoint = data[this.offset];
+                const trie = new Trie(this.tree, this.offset + 1);
+                return [[codePoint, Promise.resolve(trie)]];
+            }
+        }
+
+        /**
+         * Returns a single node that is a direct child of this node.
+         * @param {number} byte
+         * @returns {Promise<Trie>?}
+         */
+        child(byte) {
+            if (this.offset === this.tree.data.length) {
+                const i = this.tree.branches.getIndex(byte);
+                if (i !== -1) {
+                    let branch = this.tree.branches.subtrees[i];
+                    if (branch === null) {
+                        const newnode = this.tree.branches.getNodeID(i);
+                        if (!newnode) {
+                            throw new Error(`malformed tree; no node for key ${byte}`);
+                        }
+                        branch = registry.searchTreeLoadByNodeID(newnode);
+                        this.tree.branches.subtrees[i] = branch;
+                        const mhpI = this.tree.might_have_prefix_branches.getIndex(byte);
+                        if (mhpI !== -1) {
+                            this.tree.might_have_prefix_branches.subtrees[mhpI] = branch;
+                        }
+                    }
+                    return branch.then(branch => branch.trie());
+                }
+            } else if (this.tree.data[this.offset] === byte) {
+                return Promise.resolve(new Trie(this.tree, this.offset + 1));
+            }
+            return null;
+        }
+    }
+
+    class DataColumn {
+        /**
+         * Construct the wrapper object for a data column.
+         * @param {number[]} counts
+         * @param {Uint8Array} hashes
+         * @param {RoaringBitmap} emptyset
+         * @param {string} name
+         */
+        constructor(counts, hashes, emptyset, name) {
+            this.hashes = hashes;
+            this.emptyset = emptyset;
+            this.name = name;
+            /** @type {{"hash": Uint8Array, "data": Promise<Uint8Array[]>?, "end": number}[]} */
+            this.buckets = [];
+            this.bucket_keys = [];
+            const l = counts.length;
+            let k = 0;
+            let totalLength = 0;
+            for (let i = 0; i < l; ++i) {
+                const count = counts[i];
+                totalLength += count;
+                const start = k;
+                for (let j = 0; j < count; ++j) {
+                    if (emptyset.contains(k)) {
+                        j -= 1;
+                    }
+                    k += 1;
+                }
+                const end = k;
+                const bucket = {hash: hashes.subarray(i * 6, (i + 1) * 6), data: null, end, count};
+                this.buckets.push(bucket);
+                this.bucket_keys.push(start);
+            }
+            this.length = totalLength;
+        }
+        /**
+         * Check if a cell contains the empty string.
+         * @param {number} id
+         * @returns {boolean}
+         */
+        isEmpty(id) {
+            return this.emptyset.contains(id);
+        }
+        /**
+         * Look up a cell by row ID.
+         * @param {number} id
+         * @returns {Promise<Uint8Array|undefined>}
+         */
+        async at(id) {
+            if (this.emptyset.contains(id)) {
+                return Promise.resolve(EMPTY_UINT8);
+            } else {
+                let idx = -1;
+                while (this.bucket_keys[idx + 1] <= id) {
+                    idx += 1;
+                }
+                if (idx === -1 || idx >= this.bucket_keys.length) {
+                    return Promise.resolve(undefined);
+                } else {
+                    const start = this.bucket_keys[idx];
+                    const {hash, end} = this.buckets[idx];
+                    let data = this.buckets[idx].data;
+                    if (data === null) {
+                        const dataSansEmptyset = await registry.dataLoadByNameAndHash(
+                            this.name,
+                            hash,
+                        );
+                        // After the `await` resolves, another task might fill
+                        // in the data. If so, we should use that.
+                        data = this.buckets[idx].data;
+                        if (data !== null) {
+                            return (await data)[id - start];
+                        }
+                        /** @type {(Uint8Array[])|null} */
+                        let dataWithEmptyset = null;
+                        let pos = start;
+                        let insertCount = 0;
+                        while (pos < end) {
+                            if (this.emptyset.contains(pos)) {
+                                if (dataWithEmptyset === null) {
+                                    dataWithEmptyset = dataSansEmptyset.splice(0, insertCount);
+                                } else if (insertCount !== 0) {
+                                    dataWithEmptyset.push(
+                                        ...dataSansEmptyset.splice(0, insertCount),
+                                    );
+                                }
+                                insertCount = 0;
+                                dataWithEmptyset.push(EMPTY_UINT8);
+                            } else {
+                                insertCount += 1;
+                            }
+                            pos += 1;
+                        }
+                        data = Promise.resolve(
+                            dataWithEmptyset === null ?
+                                dataSansEmptyset :
+                                dataWithEmptyset.concat(dataSansEmptyset),
+                        );
+                        this.buckets[idx].data = data;
+                    }
+                    return (await data)[id - start];
+                }
+            }
+        }
+    }
+
+    class Database {
+        /**
+         * The primary frontend for accessing data in this index.
+         *
+         * @param {Map<string, SearchTree>} searchTreeRoots
+         * @param {Map<string, DataColumn>} dataColumns
+         */
+        constructor(searchTreeRoots, dataColumns) {
+            this.searchTreeRoots = searchTreeRoots;
+            this.dataColumns = dataColumns;
+        }
+        /**
+         * Search a column by name, returning verbatim matched IDs.
+         * @param {string} colname
+         * @returns {SearchTree|undefined}
+         */
+        getIndex(colname) {
+            return this.searchTreeRoots.get(colname);
+        }
+        /**
+         * Look up a cell by column ID and row ID.
+         * @param {string} colname
+         * @returns {DataColumn|undefined}
+         */
+        getData(colname) {
+            return this.dataColumns.get(colname);
+        }
+    }
+
+    /**
+     * Load a data column.
+     * @param {Uint8Array} data
+     */
+    function loadColumnFromBytes(data) {
+        const hashBuf = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0);
+        const truncatedHash = hashBuf.subarray(2, 8);
+        siphashOfBytes(data, 0, 0, 0, 0, hashBuf);
+        const cb = registry.dataColumnLoadPromiseCallbacks.get(truncatedHash);
+        if (cb) {
+            const backrefs = [];
+            const dataSansEmptyset = [];
+            let i = 0;
+            const l = data.length;
+            while (i < l) {
+                let c = data[i];
+                if (c >= 48 && c <= 63) { // 48 = "0", 63 = "?"
+                    dataSansEmptyset.push(backrefs[c - 48]);
+                    i += 1;
+                } else {
+                    let n = 0;
+                    while (c < 96) { // 96 = "`"
+                        n = (n << 4) | (c & 0xF);
+                        i += 1;
+                        c = data[i];
+                    }
+                    n = (n << 4) | (c & 0xF);
+                    i += 1;
+                    const item = data.subarray(i, i + n);
+                    dataSansEmptyset.push(item);
+                    i += n;
+                    backrefs.unshift(item);
+                    if (backrefs.length > 16) {
+                        backrefs.pop();
+                    }
+                }
+            }
+            cb(null, dataSansEmptyset);
+        }
+    }
+
+    /**
+     * @param {string} inputBase64
+     * @returns {[Uint8Array, SearchTree]}
+     */
+    function makeSearchTreeFromBase64(inputBase64) {
+        const input = makeUint8ArrayFromBase64(inputBase64);
+        let i = 0;
+        const l = input.length;
+        /** @type {HashTable<SearchTree>} */
+        const stash = new HashTable();
+        const hash = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0);
+        const truncatedHash = new Uint8Array(hash.buffer, 2, 6);
+        // used for handling compressed (that is, relative-offset) nodes
+        /** @type {{hash: Uint8Array, used: boolean}[]} */
+        const hash_history = [];
+        /** @type {Uint8Array[]} */
+        const data_history = [];
+        let canonical = EMPTY_UINT8;
+        /** @type {SearchTree} */
+        let tree = new SearchTree(
+            EMPTY_SEARCH_TREE_BRANCHES,
+            EMPTY_SEARCH_TREE_BRANCHES,
+            EMPTY_UINT8,
+            EMPTY_BITMAP,
+            EMPTY_BITMAP,
+        );
+        /**
+         * @param {Uint8Array} input
+         * @param {number} i
+         * @param {number} compression_tag
+         * @returns {{
+         *     "cpbranches": Uint8Array,
+         *     "csbranches": Uint8Array,
+         *     "might_have_prefix_branches": SearchTreeBranches,
+         *     "branches": SearchTreeBranches,
+         *     "cpnodes": Uint8Array,
+         *     "csnodes": Uint8Array,
+         *     "consumed_len_bytes": number,
+         * }}
+         */
+        function makeBranchesFromBinaryData(
+            input,
+            i,
+            compression_tag,
+        ) {
+            const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0x00;
+            const is_stack_compressed = (compression_tag & 0x02) !== 0;
+            const is_long_compressed = (compression_tag & 0x04) !== 0;
+            const all_children_are_compressed =
+                (compression_tag & 0xF0) === 0xF0 && !is_long_compressed;
+            const any_children_are_compressed =
+                (compression_tag & 0xF0) !== 0x00 || is_long_compressed;
+            const start_point = i;
+            let cplen;
+            let cslen;
+            let alphabitmap = null;
+            if (is_pure_suffixes_only_node) {
+                cplen = 0;
+                cslen = input[i];
+                i += 1;
+                if (cslen >= 0xc0) {
+                    alphabitmap = SearchTreeBranchesLongAlphaBitmap;
+                    cslen = cslen & 0x3F;
+                } else if (cslen >= 0x80) {
+                    alphabitmap = SearchTreeBranchesShortAlphaBitmap;
+                    cslen = cslen & 0x7F;
+                }
+            } else {
+                cplen = input[i];
+                i += 1;
+                cslen = input[i];
+                i += 1;
+                if (cplen === 0xff && cslen === 0xff) {
+                    cplen = 0x100;
+                    cslen = 0;
+                } else if (cplen >= 0xc0 && cslen >= 0xc0) {
+                    alphabitmap = SearchTreeBranchesLongAlphaBitmap;
+                    cplen = cplen & 0x3F;
+                    cslen = cslen & 0x3F;
+                } else if (cplen >= 0x80 && cslen >= 0x80) {
+                    alphabitmap = SearchTreeBranchesShortAlphaBitmap;
+                    cplen = cplen & 0x7F;
+                    cslen = cslen & 0x7F;
+                }
+            }
+            let j = 0;
+            /** @type {Uint8Array} */
+            let cpnodes;
+            if (any_children_are_compressed) {
+                cpnodes = cplen === 0 ? EMPTY_UINT8 : new Uint8Array(cplen * 6);
+                while (j < cplen) {
+                    const is_compressed = all_children_are_compressed ||
+                        ((0x10 << j) & compression_tag) !== 0;
+                    if (is_compressed) {
+                        let slot = hash_history.length - 1;
+                        if (is_stack_compressed) {
+                            while (hash_history[slot].used) {
+                                slot -= 1;
+                            }
+                        } else {
+                            slot -= input[i];
+                            i += 1;
+                        }
+                        hash_history[slot].used = true;
+                        cpnodes.set(
+                            hash_history[slot].hash,
+                            j * 6,
+                        );
+                    } else {
+                        const joff = j * 6;
+                        cpnodes[joff + 0] = input[i + 0];
+                        cpnodes[joff + 1] = input[i + 1];
+                        cpnodes[joff + 2] = input[i + 2];
+                        cpnodes[joff + 3] = input[i + 3];
+                        cpnodes[joff + 4] = input[i + 4];
+                        cpnodes[joff + 5] = input[i + 5];
+                        i += 6;
+                    }
+                    j += 1;
+                }
+            } else {
+                cpnodes = cplen === 0 ? EMPTY_UINT8 : input.subarray(i, i + (cplen * 6));
+                i += cplen * 6;
+            }
+            j = 0;
+            /** @type {Uint8Array} */
+            let csnodes;
+            if (any_children_are_compressed) {
+                csnodes = cslen === 0 ? EMPTY_UINT8 : new Uint8Array(cslen * 6);
+                while (j < cslen) {
+                    const is_compressed = all_children_are_compressed ||
+                        ((0x10 << (cplen + j)) & compression_tag) !== 0;
+                    if (is_compressed) {
+                        let slot = hash_history.length - 1;
+                        if (is_stack_compressed) {
+                            while (hash_history[slot].used) {
+                                slot -= 1;
+                            }
+                        } else {
+                            slot -= input[i];
+                            i += 1;
+                        }
+                        hash_history[slot].used = true;
+                        csnodes.set(
+                            hash_history[slot].hash,
+                            j * 6,
+                        );
+                    } else {
+                        const joff = j * 6;
+                        csnodes[joff + 0] = input[i + 0];
+                        csnodes[joff + 1] = input[i + 1];
+                        csnodes[joff + 2] = input[i + 2];
+                        csnodes[joff + 3] = input[i + 3];
+                        csnodes[joff + 4] = input[i + 4];
+                        csnodes[joff + 5] = input[i + 5];
+                        i += 6;
+                    }
+                    j += 1;
+                }
+            } else {
+                csnodes = cslen === 0 ? EMPTY_UINT8 : input.subarray(i, i + (cslen * 6));
+                i += cslen * 6;
+            }
+            let cpbranches;
+            let might_have_prefix_branches;
+            if (cplen === 0) {
+                cpbranches = EMPTY_UINT8;
+                might_have_prefix_branches = EMPTY_SEARCH_TREE_BRANCHES;
+            } else if (alphabitmap) {
+                cpbranches = new Uint8Array(input.buffer, i + input.byteOffset, alphabitmap.width);
+                const branchset = (alphabitmap.width === 4 ? (input[i + 3] << 24) : 0) |
+                    (input[i + 2] << 16) |
+                    (input[i + 1] << 8) |
+                    input[i];
+                might_have_prefix_branches = new alphabitmap(branchset, cpnodes);
+                i += alphabitmap.width;
+            } else {
+                cpbranches = new Uint8Array(input.buffer, i + input.byteOffset, cplen);
+                might_have_prefix_branches = new SearchTreeBranchesArray(cpbranches, cpnodes);
+                i += cplen;
+            }
+            let csbranches;
+            let branches;
+            if (cslen === 0) {
+                csbranches = EMPTY_UINT8;
+                branches = might_have_prefix_branches;
+            } else if (alphabitmap) {
+                csbranches = new Uint8Array(input.buffer, i + input.byteOffset, alphabitmap.width);
+                const branchset = (alphabitmap.width === 4 ? (input[i + 3] << 24) : 0) |
+                    (input[i + 2] << 16) |
+                    (input[i + 1] << 8) |
+                    input[i];
+                if (cplen === 0) {
+                    branches = new alphabitmap(branchset, csnodes);
+                } else {
+                    const cpoffset = i - alphabitmap.width;
+                    const cpbranchset =
+                        (alphabitmap.width === 4 ? (input[cpoffset + 3] << 24) : 0) |
+                        (input[cpoffset + 2] << 16) |
+                        (input[cpoffset + 1] << 8) |
+                        input[cpoffset];
+                    const hashes = new Uint8Array((cplen + cslen) * 6);
+                    let cpi = 0;
+                    let csi = 0;
+                    let j = 0;
+                    for (let k = 0; k < alphabitmap.ALPHABITMAP_CHARS.length; k += 1) {
+                        if (branchset & (1 << k)) {
+                            hashes[j + 0] = csnodes[csi + 0];
+                            hashes[j + 1] = csnodes[csi + 1];
+                            hashes[j + 2] = csnodes[csi + 2];
+                            hashes[j + 3] = csnodes[csi + 3];
+                            hashes[j + 4] = csnodes[csi + 4];
+                            hashes[j + 5] = csnodes[csi + 5];
+                            j += 6;
+                            csi += 6;
+                        } else if (cpbranchset & (1 << k)) {
+                            hashes[j + 0] = cpnodes[cpi + 0];
+                            hashes[j + 1] = cpnodes[cpi + 1];
+                            hashes[j + 2] = cpnodes[cpi + 2];
+                            hashes[j + 3] = cpnodes[cpi + 3];
+                            hashes[j + 4] = cpnodes[cpi + 4];
+                            hashes[j + 5] = cpnodes[cpi + 5];
+                            j += 6;
+                            cpi += 6;
+                        }
+                    }
+                    branches = new alphabitmap(branchset | cpbranchset, hashes);
+                }
+                i += alphabitmap.width;
+            } else {
+                csbranches = new Uint8Array(input.buffer, i + input.byteOffset, cslen);
+                if (cplen === 0) {
+                    branches = new SearchTreeBranchesArray(csbranches, csnodes);
+                } else {
+                    const branchset = new Uint8Array(cplen + cslen);
+                    const hashes = new Uint8Array((cplen + cslen) * 6);
+                    let cpi = 0;
+                    let csi = 0;
+                    let j = 0;
+                    while (cpi < cplen || csi < cslen) {
+                        if (cpi >= cplen || (csi < cslen && cpbranches[cpi] > csbranches[csi])) {
+                            branchset[j] = csbranches[csi];
+                            const joff = j * 6;
+                            const csioff = csi * 6;
+                            hashes[joff + 0] = csnodes[csioff + 0];
+                            hashes[joff + 1] = csnodes[csioff + 1];
+                            hashes[joff + 2] = csnodes[csioff + 2];
+                            hashes[joff + 3] = csnodes[csioff + 3];
+                            hashes[joff + 4] = csnodes[csioff + 4];
+                            hashes[joff + 5] = csnodes[csioff + 5];
+                            csi += 1;
+                        } else {
+                            branchset[j] = cpbranches[cpi];
+                            const joff = j * 6;
+                            const cpioff = cpi * 6;
+                            hashes[joff + 0] = cpnodes[cpioff + 0];
+                            hashes[joff + 1] = cpnodes[cpioff + 1];
+                            hashes[joff + 2] = cpnodes[cpioff + 2];
+                            hashes[joff + 3] = cpnodes[cpioff + 3];
+                            hashes[joff + 4] = cpnodes[cpioff + 4];
+                            hashes[joff + 5] = cpnodes[cpioff + 5];
+                            cpi += 1;
+                        }
+                        j += 1;
+                    }
+                    branches = new SearchTreeBranchesArray(branchset, hashes);
+                }
+                i += cslen;
+            }
+            return {
+                consumed_len_bytes: i - start_point,
+                cpbranches,
+                csbranches,
+                cpnodes,
+                csnodes,
+                branches,
+                might_have_prefix_branches,
+            };
+        }
+        while (i < l) {
+            const start = i;
+            let data;
+            // compression_tag = 1 means pure-suffixes-only,
+            // which is not considered "compressed" for the purposes of this loop
+            // because that's the canonical, hashed version of the data
+            let compression_tag = input[i];
+            const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0;
+            if (compression_tag > 1) {
+                // compressed node
+                const is_long_compressed = (compression_tag & 0x04) !== 0;
+                const is_data_compressed = (compression_tag & 0x08) !== 0;
+                i += 1;
+                if (is_long_compressed) {
+                    compression_tag |= input[i] << 8;
+                    i += 1;
+                    compression_tag |= input[i] << 16;
+                    i += 1;
+                }
+                let dlen = input[i];
+                i += 1;
+                if (is_data_compressed) {
+                    data = data_history[data_history.length - dlen - 1];
+                    dlen = data.length;
+                } else {
+                    data = dlen === 0 ?
+                        EMPTY_UINT8 :
+                        new Uint8Array(input.buffer, i + input.byteOffset, dlen);
+                    i += dlen;
+                }
+                const coffset = i;
+                const {
+                    cpbranches,
+                    csbranches,
+                    cpnodes,
+                    csnodes,
+                    consumed_len_bytes: branches_consumed_len_bytes,
+                    branches,
+                    might_have_prefix_branches,
+                } = makeBranchesFromBinaryData(input, i, compression_tag);
+                i += branches_consumed_len_bytes;
+                let whole;
+                let suffix;
+                if (is_pure_suffixes_only_node) {
+                    whole = EMPTY_BITMAP;
+                    suffix = input[i] === 0 ?
+                        EMPTY_BITMAP1 :
+                        new RoaringBitmap(input, i);
+                    i += suffix.consumed_len_bytes;
+                } else if (input[i] === 0xff) {
+                    whole = EMPTY_BITMAP;
+                    suffix = EMPTY_BITMAP1;
+                    i += 1;
+                } else {
+                    whole = input[i] === 0 ?
+                        EMPTY_BITMAP1 :
+                        new RoaringBitmap(input, i);
+                    i += whole.consumed_len_bytes;
+                    suffix = input[i] === 0 ?
+                        EMPTY_BITMAP1 :
+                        new RoaringBitmap(input, i);
+                    i += suffix.consumed_len_bytes;
+                }
+                tree = new SearchTree(
+                    branches,
+                    might_have_prefix_branches,
+                    data,
+                    whole,
+                    suffix,
+                );
+                const clen = (
+                    (is_pure_suffixes_only_node ? 3 : 4) + // lengths of children and data
+                    dlen +
+                    cpnodes.length + csnodes.length +
+                    cpbranches.length + csbranches.length +
+                    whole.consumed_len_bytes +
+                    suffix.consumed_len_bytes
+                );
+                if (canonical.length < clen) {
+                    canonical = new Uint8Array(clen);
+                }
+                let ci = 0;
+                canonical[ci] = is_pure_suffixes_only_node ? 1 : 0;
+                ci += 1;
+                canonical[ci] = dlen;
+                ci += 1;
+                canonical.set(data, ci);
+                ci += dlen;
+                canonical[ci] = input[coffset];
+                ci += 1;
+                if (!is_pure_suffixes_only_node) {
+                    canonical[ci] = input[coffset + 1];
+                    ci += 1;
+                }
+                canonical.set(cpnodes, ci);
+                ci += cpnodes.length;
+                canonical.set(csnodes, ci);
+                ci += csnodes.length;
+                canonical.set(cpbranches, ci);
+                ci += cpbranches.length;
+                canonical.set(csbranches, ci);
+                ci += csbranches.length;
+                const leavesOffset = i - whole.consumed_len_bytes - suffix.consumed_len_bytes;
+                for (let j = leavesOffset; j < i; j += 1) {
+                    canonical[ci + j - leavesOffset] = input[j];
+                }
+                siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash);
+                hash[2] &= 0x7f;
+            } else {
+                // uncompressed node
+                const dlen = input [i + 1];
+                i += 2;
+                if (dlen === 0) {
+                    data = EMPTY_UINT8;
+                } else {
+                    data = new Uint8Array(input.buffer, i + input.byteOffset, dlen);
+                }
+                i += dlen;
+                const {
+                    consumed_len_bytes: branches_consumed_len_bytes,
+                    branches,
+                    might_have_prefix_branches,
+                } = makeBranchesFromBinaryData(input, i, compression_tag);
+                i += branches_consumed_len_bytes;
+                let whole;
+                let suffix;
+                if (is_pure_suffixes_only_node) {
+                    whole = EMPTY_BITMAP;
+                    suffix = input[i] === 0 ?
+                        EMPTY_BITMAP1 :
+                        new RoaringBitmap(input, i);
+                    i += suffix.consumed_len_bytes;
+                } else if (input[i] === 0xff) {
+                    whole = EMPTY_BITMAP;
+                    suffix = EMPTY_BITMAP;
+                    i += 1;
+                } else {
+                    whole = input[i] === 0 ?
+                        EMPTY_BITMAP1 :
+                        new RoaringBitmap(input, i);
+                    i += whole.consumed_len_bytes;
+                    suffix = input[i] === 0 ?
+                        EMPTY_BITMAP1 :
+                        new RoaringBitmap(input, i);
+                    i += suffix.consumed_len_bytes;
+                }
+                siphashOfBytes(new Uint8Array(
+                    input.buffer,
+                    start + input.byteOffset,
+                    i - start,
+                ), 0, 0, 0, 0, hash);
+                hash[2] &= 0x7f;
+                tree = new SearchTree(
+                    branches,
+                    might_have_prefix_branches,
+                    data,
+                    whole,
+                    suffix,
+                );
+            }
+            hash_history.push({hash: truncatedHash.slice(), used: false});
+            if (data.length !== 0) {
+                data_history.push(data);
+            }
+            const tree_branch_nodeids = tree.branches.nodeids;
+            const tree_branch_subtrees = tree.branches.subtrees;
+            let j = 0;
+            let lb = tree.branches.subtrees.length;
+            while (j < lb) {
+                // node id with a 1 in its most significant bit is inlined, and, so
+                // it won't be in the stash
+                if ((tree_branch_nodeids[j * 6] & 0x80) === 0) {
+                    const subtree = stash.getWithOffsetKey(tree_branch_nodeids, j * 6);
+                    if (subtree !== undefined) {
+                        tree_branch_subtrees[j] = Promise.resolve(subtree);
+                    }
+                }
+                j += 1;
+            }
+            const tree_mhp_branch_nodeids = tree.might_have_prefix_branches.nodeids;
+            const tree_mhp_branch_subtrees = tree.might_have_prefix_branches.subtrees;
+            j = 0;
+            lb = tree.might_have_prefix_branches.subtrees.length;
+            while (j < lb) {
+                // node id with a 1 in its most significant bit is inlined, and, so
+                // it won't be in the stash
+                if ((tree_mhp_branch_nodeids[j * 6] & 0x80) === 0) {
+                    const subtree = stash.getWithOffsetKey(tree_mhp_branch_nodeids, j * 6);
+                    if (subtree !== undefined) {
+                        tree_mhp_branch_subtrees[j] = Promise.resolve(subtree);
+                    }
+                }
+                j += 1;
+            }
+            if (i !== l) {
+                stash.set(truncatedHash, tree);
+            }
+        }
+        return [truncatedHash, tree];
+    }
+
+    return new Promise((resolve, reject) => {
+        registry.searchTreeRootCallback = (error, data) => {
+            if (data) {
+                resolve(data);
+            } else {
+                reject(error);
+            }
+        };
+        hooks.loadRoot(callbacks);
+    });
+}
+
+if (typeof window !== "undefined") {
+    window.Stringdex = {
+        loadDatabase,
+    };
+    window.RoaringBitmap = RoaringBitmap;
+    if (window.StringdexOnload) {
+        window.StringdexOnload.forEach(cb => cb(window.Stringdex));
+    }
+} else {
+    /** @type {stringdex.Stringdex} */
+    // eslint-disable-next-line no-undef
+    module.exports.Stringdex = {
+        loadDatabase,
+    };
+    /** @type {stringdex.RoaringBitmap} */
+    // eslint-disable-next-line no-undef
+    module.exports.RoaringBitmap = RoaringBitmap;
+}
+
+// eslint-disable-next-line max-len
+// polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64
+/**
+ * @type {function(string): Uint8Array} base64
+ */
+//@ts-expect-error
+const makeUint8ArrayFromBase64 = Uint8Array.fromBase64 ? Uint8Array.fromBase64 : (string => {
+    const bytes_as_string = atob(string);
+    const l = bytes_as_string.length;
+    const bytes = new Uint8Array(l);
+    for (let i = 0; i < l; ++i) {
+        bytes[i] = bytes_as_string.charCodeAt(i);
+    }
+    return bytes;
+});
+/**
+ * @type {function(string): Uint8Array} base64
+ */
+//@ts-expect-error
+const makeUint8ArrayFromHex = Uint8Array.fromHex ? Uint8Array.fromHex : (string => {
+    /** @type {Object<string, number>} */
+    const alpha = {
+        "0": 0, "1": 1,
+        "2": 2, "3": 3,
+        "4": 4, "5": 5,
+        "6": 6, "7": 7,
+        "8": 8, "9": 9,
+        "a": 10, "b": 11,
+        "A": 10, "B": 11,
+        "c": 12, "d": 13,
+        "C": 12, "D": 13,
+        "e": 14, "f": 15,
+        "E": 14, "F": 15,
+    };
+    const l = string.length >> 1;
+    const bytes = new Uint8Array(l);
+    for (let i = 0; i < l; i += 1) {
+        const top = string[i << 1];
+        const bottom = string[(i << 1) + 1];
+        bytes[i] = (alpha[top] << 4) | alpha[bottom];
+    }
+    return bytes;
+});
+
+/**
+ * @type {function(Uint8Array): string} base64
+ */
+//@ts-expect-error
+const makeHexFromUint8Array = Uint8Array.prototype.toHex ? (array => array.toHex()) : (array => {
+    /** @type {string[]} */
+    const alpha = [
+        "0", "1",
+        "2", "3",
+        "4", "5",
+        "6", "7",
+        "8", "9",
+        "a", "b",
+        "c", "d",
+        "e", "f",
+    ];
+    const l = array.length;
+    const v = [];
+    for (let i = 0; i < l; ++i) {
+        v.push(alpha[array[i] >> 4]);
+        v.push(alpha[array[i] & 0xf]);
+    }
+    return v.join("");
+});
+
+//////////////
+
+/**
+ * SipHash 1-3
+ * @param {Uint8Array} input data to be hashed; all codepoints in string should be less than 256
+ * @param {number} k0lo first word of key
+ * @param {number} k0hi second word of key
+ * @param {number} k1lo third word of key
+ * @param {number} k1hi fourth word of key
+ * @param {Uint8Array} output the data to write (clobber the first eight bytes)
+ */
+function siphashOfBytes(input, k0lo, k0hi, k1lo, k1hi, output) {
+    // hash state
+    // While siphash uses 64 bit state, js only has native support
+    // for 32 bit numbers. BigInt, unfortunately, doesn't count.
+    // It's too slow.
+    let v0lo = k0lo ^ 0x70736575;
+    let v0hi = k0hi ^ 0x736f6d65;
+    let v1lo = k1lo ^ 0x6e646f6d;
+    let v1hi = k1hi ^ 0x646f7261;
+    let v2lo = k0lo ^ 0x6e657261;
+    let v2hi = k0hi ^ 0x6c796765;
+    let v3lo = k1lo ^ 0x79746573;
+    let v3hi = k1hi ^ 0x74656462;
+    const inputLength = input.length;
+    let inputI = 0;
+    // main hash loop
+    const left = inputLength & 0x7;
+    let milo = 0;
+    let mihi = 0;
+    while (inputI < inputLength - left) {
+        u8ToU64le(inputI, inputI + 8);
+        v3lo ^= milo;
+        v3hi ^= mihi;
+        siphashCompress();
+        v0lo ^= milo;
+        v0hi ^= mihi;
+        inputI += 8;
+    }
+    u8ToU64le(inputI, inputI + left);
+    // finish
+    const blo = milo;
+    const bhi = ((inputLength & 0xff) << 24) | mihi;
+    v3lo ^= blo;
+    v3hi ^= bhi;
+    siphashCompress();
+    v0lo ^= blo;
+    v0hi ^= bhi;
+    v2lo ^= 0xff;
+    siphashCompress();
+    siphashCompress();
+    siphashCompress();
+    output[7] = (v0lo ^ v1lo ^ v2lo ^ v3lo) & 0xff;
+    output[6] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 8;
+    output[5] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 16;
+    output[4] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 24;
+    output[3] = (v0hi ^ v1hi ^ v2hi ^ v3hi) & 0xff;
+    output[2] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 8;
+    output[1] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 16;
+    output[0] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 24;
+    /**
+     * Convert eight bytes to a single 64-bit number
+     * @param {number} offset
+     * @param {number} length
+     */
+    function u8ToU64le(offset, length) {
+        const n0 = offset < length ? input[offset] & 0xff : 0;
+        const n1 = offset + 1 < length ? input[offset + 1] & 0xff : 0;
+        const n2 = offset + 2 < length ? input[offset + 2] & 0xff : 0;
+        const n3 = offset + 3 < length ? input[offset + 3] & 0xff : 0;
+        const n4 = offset + 4 < length ? input[offset + 4] & 0xff : 0;
+        const n5 = offset + 5 < length ? input[offset + 5] & 0xff : 0;
+        const n6 = offset + 6 < length ? input[offset + 6] & 0xff : 0;
+        const n7 = offset + 7 < length ? input[offset + 7] & 0xff : 0;
+        milo = n0 | (n1 << 8) | (n2 << 16) | (n3 << 24);
+        mihi = n4 | (n5 << 8) | (n6 << 16) | (n7 << 24);
+    }
+    function siphashCompress() {
+        // v0 += v1;
+        v0hi = (v0hi + v1hi + (((v0lo >>> 0) + (v1lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0;
+        v0lo = (v0lo + v1lo) | 0;
+        // rotl(v1, 13)
+        let v1lo_ = v1lo;
+        let v1hi_ = v1hi;
+        v1lo = (v1lo_ << 13) | (v1hi_ >>> 19);
+        v1hi = (v1hi_ << 13) | (v1lo_ >>> 19);
+        // v1 ^= v0
+        v1lo ^= v0lo;
+        v1hi ^= v0hi;
+        // rotl(v0, 32)
+        const v0lo_ = v0lo;
+        const v0hi_ = v0hi;
+        v0lo = v0hi_;
+        v0hi = v0lo_;
+        // v2 += v3
+        v2hi = (v2hi + v3hi + (((v2lo >>> 0) + (v3lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0;
+        v2lo = (v2lo + v3lo) | 0;
+        // rotl(v3, 16)
+        let v3lo_ = v3lo;
+        let v3hi_ = v3hi;
+        v3lo = (v3lo_ << 16) | (v3hi_ >>> 16);
+        v3hi = (v3hi_ << 16) | (v3lo_ >>> 16);
+        // v3 ^= v2
+        v3lo ^= v2lo;
+        v3hi ^= v2hi;
+        // v0 += v3
+        v0hi = (v0hi + v3hi + (((v0lo >>> 0) + (v3lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0;
+        v0lo = (v0lo + v3lo) | 0;
+        // rotl(v3, 21)
+        v3lo_ = v3lo;
+        v3hi_ = v3hi;
+        v3lo = (v3lo_ << 21) | (v3hi_ >>> 11);
+        v3hi = (v3hi_ << 21) | (v3lo_ >>> 11);
+        // v3 ^= v0
+        v3lo ^= v0lo;
+        v3hi ^= v0hi;
+        // v2 += v1
+        v2hi = (v2hi + v1hi + (((v2lo >>> 0) + (v1lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0;
+        v2lo = (v2lo + v1lo) | 0;
+        // rotl(v1, 17)
+        v1lo_ = v1lo;
+        v1hi_ = v1hi;
+        v1lo = (v1lo_ << 17) | (v1hi_ >>> 15);
+        v1hi = (v1hi_ << 17) | (v1lo_ >>> 15);
+        // v1 ^= v2
+        v1lo ^= v2lo;
+        v1hi ^= v2hi;
+        // rotl(v2, 32)
+        const v2lo_ = v2lo;
+        const v2hi_ = v2hi;
+        v2lo = v2hi_;
+        v2hi = v2lo_;
+    }
+}
+
+//////////////
+
+
+// Parts of this code are based on Lucene, which is licensed under the
+// Apache/2.0 license.
+// More information found here:
+// https://fossies.org/linux/lucene/lucene/core/src/java/org/apache/lucene/util/automaton/
+//   LevenshteinAutomata.java
+class ParametricDescription {
+    /**
+     * @param {number} w
+     * @param {number} n
+     * @param {Int32Array} minErrors
+     */
+    constructor(w, n, minErrors) {
+        this.w = w;
+        this.n = n;
+        this.minErrors = minErrors;
+    }
+    /**
+     * @param {number} absState
+     * @returns {boolean}
+     */
+    isAccept(absState) {
+        const state = Math.floor(absState / (this.w + 1));
+        const offset = absState % (this.w + 1);
+        return this.w - offset + this.minErrors[state] <= this.n;
+    }
+    /**
+     * @param {number} absState
+     * @returns {number}
+     */
+    getPosition(absState) {
+        return absState % (this.w + 1);
+    }
+    /**
+     * @param {Uint8Array} name
+     * @param {number} charCode
+     * @param {number} pos
+     * @param {number} end
+     * @returns {number}
+     */
+    getVector(name, charCode, pos, end) {
+        let vector = 0;
+        for (let i = pos; i < end; i += 1) {
+            vector = vector << 1;
+            if (name[i] === charCode) {
+                vector |= 1;
+            }
+        }
+        return vector;
+    }
+    /**
+     * @param {Int32Array} data
+     * @param {number} index
+     * @param {number} bitsPerValue
+     * @returns {number}
+     */
+    unpack(data, index, bitsPerValue) {
+        const bitLoc = (bitsPerValue * index);
+        const dataLoc = bitLoc >> 5;
+        const bitStart = bitLoc & 31;
+        if (bitStart + bitsPerValue <= 32) {
+            // not split
+            return ((data[dataLoc] >> bitStart) & this.MASKS[bitsPerValue - 1]);
+        } else {
+            // split
+            const part = 32 - bitStart;
+            return ~~(((data[dataLoc] >> bitStart) & this.MASKS[part - 1]) +
+                ((data[1 + dataLoc] & this.MASKS[bitsPerValue - part - 1]) << part));
+        }
+    }
+}
+ParametricDescription.prototype.MASKS = new Int32Array([
+    0x1, 0x3, 0x7, 0xF,
+    0x1F, 0x3F, 0x7F, 0xFF,
+    0x1FF, 0x3F, 0x7FF, 0xFFF,
+    0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
+    0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF,
+    0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF,
+    0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF,
+    0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF,
+]);
+
+// The following code was generated with the moman/finenight pkg
+// This package is available under the MIT License, see NOTICE.txt
+// for more details.
+// This class is auto-generated, Please do not modify it directly.
+// You should modify the https://gitlab.com/notriddle/createAutomata.py instead.
+// The following code was generated with the moman/finenight pkg
+// This package is available under the MIT License, see NOTICE.txt
+// for more details.
+// This class is auto-generated, Please do not modify it directly.
+// You should modify https://gitlab.com/notriddle/moman-rustdoc instead.
+
+class Lev2TParametricDescription extends ParametricDescription {
+    /**
+     * @param {number} absState
+     * @param {number} position
+     * @param {number} vector
+     * @returns {number}
+    */
+    transition(absState, position, vector) {
+        let state = Math.floor(absState / (this.w + 1));
+        let offset = absState % (this.w + 1);
+
+        if (position === this.w) {
+            if (state < 3) {
+                const loc = Math.imul(vector, 3) + state;
+                offset += this.unpack(this.offsetIncrs0, loc, 1);
+                state = this.unpack(this.toStates0, loc, 2) - 1;
+            }
+        } else if (position === this.w - 1) {
+            if (state < 5) {
+                const loc = Math.imul(vector, 5) + state;
+                offset += this.unpack(this.offsetIncrs1, loc, 1);
+                state = this.unpack(this.toStates1, loc, 3) - 1;
+            }
+        } else if (position === this.w - 2) {
+            if (state < 13) {
+                const loc = Math.imul(vector, 13) + state;
+                offset += this.unpack(this.offsetIncrs2, loc, 2);
+                state = this.unpack(this.toStates2, loc, 4) - 1;
+            }
+        } else if (position === this.w - 3) {
+            if (state < 28) {
+                const loc = Math.imul(vector, 28) + state;
+                offset += this.unpack(this.offsetIncrs3, loc, 2);
+                state = this.unpack(this.toStates3, loc, 5) - 1;
+            }
+        } else if (position === this.w - 4) {
+            if (state < 45) {
+                const loc = Math.imul(vector, 45) + state;
+                offset += this.unpack(this.offsetIncrs4, loc, 3);
+                state = this.unpack(this.toStates4, loc, 6) - 1;
+            }
+        } else {
+            // eslint-disable-next-line no-lonely-if
+            if (state < 45) {
+                const loc = Math.imul(vector, 45) + state;
+                offset += this.unpack(this.offsetIncrs5, loc, 3);
+                state = this.unpack(this.toStates5, loc, 6) - 1;
+            }
+        }
+
+        if (state === -1) {
+            // null state
+            return -1;
+        } else {
+            // translate back to abs
+            return Math.imul(state, this.w + 1) + offset;
+        }
+    }
+
+    // state map
+    //   0 -> [(0, 0)]
+    //   1 -> [(0, 1)]
+    //   2 -> [(0, 2)]
+    //   3 -> [(0, 1), (1, 1)]
+    //   4 -> [(0, 2), (1, 2)]
+    //   5 -> [(0, 1), (1, 1), (2, 1)]
+    //   6 -> [(0, 2), (1, 2), (2, 2)]
+    //   7 -> [(0, 1), (2, 1)]
+    //   8 -> [(0, 1), (2, 2)]
+    //   9 -> [(0, 2), (2, 1)]
+    //   10 -> [(0, 2), (2, 2)]
+    //   11 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
+    //   12 -> [t(0, 2), (0, 2), (1, 2), (2, 2)]
+    //   13 -> [(0, 2), (1, 2), (2, 2), (3, 2)]
+    //   14 -> [(0, 1), (1, 1), (3, 2)]
+    //   15 -> [(0, 1), (2, 2), (3, 2)]
+    //   16 -> [(0, 1), (3, 2)]
+    //   17 -> [(0, 1), t(1, 2), (2, 2), (3, 2)]
+    //   18 -> [(0, 2), (1, 2), (3, 1)]
+    //   19 -> [(0, 2), (1, 2), (3, 2)]
+    //   20 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2)]
+    //   21 -> [(0, 2), (2, 1), (3, 1)]
+    //   22 -> [(0, 2), (2, 2), (3, 2)]
+    //   23 -> [(0, 2), (3, 1)]
+    //   24 -> [(0, 2), (3, 2)]
+    //   25 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2)]
+    //   26 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2)]
+    //   27 -> [t(0, 2), (0, 2), (1, 2), (3, 1)]
+    //   28 -> [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
+    //   29 -> [(0, 2), (1, 2), (2, 2), (4, 2)]
+    //   30 -> [(0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
+    //   31 -> [(0, 2), (1, 2), (3, 2), (4, 2)]
+    //   32 -> [(0, 2), (1, 2), (4, 2)]
+    //   33 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2), (4, 2)]
+    //   34 -> [(0, 2), (1, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
+    //   35 -> [(0, 2), (2, 1), (4, 2)]
+    //   36 -> [(0, 2), (2, 2), (3, 2), (4, 2)]
+    //   37 -> [(0, 2), (2, 2), (4, 2)]
+    //   38 -> [(0, 2), (3, 2), (4, 2)]
+    //   39 -> [(0, 2), (4, 2)]
+    //   40 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
+    //   41 -> [(0, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
+    //   42 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
+    //   43 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (4, 2)]
+    //   44 -> [t(0, 2), (0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
+
+
+    /** @param {number} w - length of word being checked */
+    constructor(w) {
+        super(w, 2, new Int32Array([
+            0,1,2,0,1,-1,0,-1,0,-1,0,-1,0,-1,-1,-1,-1,-1,-2,-1,-1,-2,-1,-2,
+            -1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,
+        ]));
+    }
+}
+
+Lev2TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
+    0xe,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
+    0x0,
+]);
+
+Lev2TParametricDescription.prototype.toStates1 = /*3 bits per value */ new Int32Array([
+    0x1a688a2c,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
+    0x3e0,
+]);
+
+Lev2TParametricDescription.prototype.toStates2 = /*4 bits per value */ new Int32Array([
+    0x70707054,0xdc07035,0x3dd3a3a,0x2323213a,
+    0x15435223,0x22545432,0x5435,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
+    0x80000,0x55582088,0x55555555,0x55,
+]);
+
+Lev2TParametricDescription.prototype.toStates3 = /*5 bits per value */ new Int32Array([
+    0x1c0380a4,0x700a570,0xca529c0,0x180a00,
+    0xa80af180,0xc5498e60,0x5a546398,0x8c4300e8,
+    0xac18c601,0xd8d43501,0x863500ad,0x51976d6a,
+    0x8ca0180a,0xc3501ac2,0xb0c5be16,0x76dda8a5,
+    0x18c4519,0xc41294a,0xe248d231,0x1086520c,
+    0xce31ac42,0x13946358,0x2d0348c4,0x6732d494,
+    0x1ad224a5,0xd635ad4b,0x520c4139,0xce24948,
+    0x22110a52,0x58ce729d,0xc41394e3,0x941cc520,
+    0x90e732d4,0x4729d224,0x39ce35ad,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
+    0x80000,0xc0c830,0x300f3c30,0x2200fcff,
+    0xcaa00a08,0x3c2200a8,0xa8fea00a,0x55555555,
+    0x55555555,0x55555555,0x55555555,0x55555555,
+    0x55555555,0x55555555,
+]);
+
+Lev2TParametricDescription.prototype.toStates4 = /*6 bits per value */ new Int32Array([
+    0x801c0144,0x1453803,0x14700038,0xc0005145,
+    0x1401,0x14,0x140000,0x0,
+    0x510000,0x6301f007,0x301f00d1,0xa186178,
+    0xc20ca0c3,0xc20c30,0xc30030c,0xc00c00cd,
+    0xf0c00c30,0x4c054014,0xc30944c3,0x55150c34,
+    0x8300550,0x430c0143,0x50c31,0xc30850c,
+    0xc3143000,0x50053c50,0x5130d301,0x850d30c2,
+    0x30a08608,0xc214414,0x43142145,0x21450031,
+    0x1400c314,0x4c143145,0x32832803,0x28014d6c,
+    0xcd34a0c3,0x1c50c76,0x1c314014,0x430c30c3,
+    0x1431,0xc300500,0xca00d303,0xd36d0e40,
+    0x90b0e400,0xcb2abb2c,0x70c20ca1,0x2c32ca2c,
+    0xcd2c70cb,0x31c00c00,0x34c2c32c,0x5583280,
+    0x558309b7,0x6cd6ca14,0x430850c7,0x51c51401,
+    0x1430c714,0xc3087,0x71451450,0xca00d30,
+    0xc26dc156,0xb9071560,0x1cb2abb2,0xc70c2144,
+    0xb1c51ca1,0x1421c70c,0xc51c00c3,0x30811c51,
+    0x24324308,0xc51031c2,0x70820820,0x5c33830d,
+    0xc33850c3,0x30c30c30,0xc30c31c,0x451450c3,
+    0x20c20c20,0xda0920d,0x5145914f,0x36596114,
+    0x51965865,0xd9643653,0x365a6590,0x51964364,
+    0x43081505,0x920b2032,0x2c718b28,0xd7242249,
+    0x35cb28b0,0x2cb3872c,0x972c30d7,0xb0c32cb2,
+    0x4e1c75c,0xc80c90c2,0x62ca2482,0x4504171c,
+    0xd65d9610,0x33976585,0xd95cb5d,0x4b5ca5d7,
+    0x73975c36,0x10308138,0xc2245105,0x41451031,
+    0x14e24208,0xc35c3387,0x51453851,0x1c51c514,
+    0xc70c30c3,0x20451450,0x14f1440c,0x4f0da092,
+    0x4513d41,0x6533944d,0x1350e658,0xe1545055,
+    0x64365a50,0x5519383,0x51030815,0x28920718,
+    0x441c718b,0x714e2422,0x1c35cb28,0x4e1c7387,
+    0xb28e1c51,0x5c70c32c,0xc204e1c7,0x81c61440,
+    0x1c62ca24,0xd04503ce,0x85d63944,0x39338e65,
+    0x8e154387,0x364b5ca3,0x38739738,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs4 = /*3 bits per value */ new Int32Array([
+    0x10000000,0xc00000,0x60061,0x400,
+    0x0,0x80010008,0x249248a4,0x8229048,
+    0x2092,0x6c3603,0xb61b6c30,0x6db6036d,
+    0xdb6c0,0x361b0180,0x91b72000,0xdb11b71b,
+    0x6db6236,0x1008200,0x12480012,0x24924906,
+    0x48200049,0x80410002,0x24000900,0x4924a489,
+    0x10822492,0x20800125,0x48360,0x9241b692,
+    0x6da4924,0x40009268,0x241b010,0x291b4900,
+    0x6d249249,0x49493423,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x2492,
+]);
+
+Lev2TParametricDescription.prototype.toStates5 = /*6 bits per value */ new Int32Array([
+    0x801c0144,0x1453803,0x14700038,0xc0005145,
+    0x1401,0x14,0x140000,0x0,
+    0x510000,0x4e00e007,0xe0051,0x3451451c,
+    0xd015000,0x30cd0000,0xc30c30c,0xc30c30d4,
+    0x40c30c30,0x7c01c014,0xc03458c0,0x185e0c07,
+    0x2830c286,0x830c3083,0xc30030,0x33430c,
+    0x30c3003,0x70051030,0x16301f00,0x8301f00d,
+    0x30a18617,0xc20ca0c,0x431420c3,0xb1450c51,
+    0x14314315,0x4f143145,0x34c05401,0x4c30944c,
+    0x55150c3,0x30830055,0x1430c014,0xc00050c3,
+    0xc30850,0xc314300,0x150053c5,0x25130d30,
+    0x5430d30c,0xc0354154,0x300d0c90,0x1cb2cd0c,
+    0xc91cb0c3,0x72c30cb2,0x14f1cb2c,0xc34c0540,
+    0x34c30944,0x82182214,0x851050c2,0x50851430,
+    0x1400c50c,0x30c5085,0x50c51450,0x150053c,
+    0xc25130d3,0x8850d30,0x1430a086,0x450c2144,
+    0x51cb1c21,0x1c91c70c,0xc71c314b,0x34c1cb1,
+    0x6c328328,0xc328014d,0x76cd34a0,0x1401c50c,
+    0xc31c3140,0x31430c30,0x14,0x30c3005,
+    0xa0ca00d3,0x535b0c,0x4d2830ca,0x514369b3,
+    0xc500d01,0x5965965a,0x30d46546,0x6435030c,
+    0x8034c659,0xdb439032,0x2c390034,0xcaaecb24,
+    0x30832872,0xcb28b1c,0x4b1c32cb,0x70030033,
+    0x30b0cb0c,0xe40ca00d,0x400d36d0,0xb2c90b0e,
+    0xca1cb2ab,0xa2c70c20,0x6575d95c,0x4315b5ce,
+    0x95c53831,0x28034c5d,0x9b705583,0xa1455830,
+    0xc76cd6c,0x40143085,0x71451c51,0x871430c,
+    0x450000c3,0xd3071451,0x1560ca00,0x560c26dc,
+    0xb35b2851,0xc914369,0x1a14500d,0x46593945,
+    0xcb2c939,0x94507503,0x328034c3,0x9b70558,
+    0xe41c5583,0x72caaeca,0x1c308510,0xc7147287,
+    0x50871c32,0x1470030c,0xd307147,0xc1560ca0,
+    0x1560c26d,0xabb2b907,0x21441cb2,0x38a1c70c,
+    0x8e657394,0x314b1c93,0x39438738,0x43083081,
+    0x31c22432,0x820c510,0x830d7082,0x50c35c33,
+    0xc30c338,0xc31c30c3,0x50c30c30,0xc204514,
+    0x890c90c2,0x31440c70,0xa8208208,0xea0df0c3,
+    0x8a231430,0xa28a28a2,0x28a28a1e,0x1861868a,
+    0x48308308,0xc3682483,0x14516453,0x4d965845,
+    0xd4659619,0x36590d94,0xd969964,0x546590d9,
+    0x20c20541,0x920d20c,0x5914f0da,0x96114514,
+    0x65865365,0xe89d3519,0x99e7a279,0x9e89e89e,
+    0x81821827,0xb2032430,0x18b28920,0x422492c7,
+    0xb28b0d72,0x3872c35c,0xc30d72cb,0x32cb2972,
+    0x1c75cb0c,0xc90c204e,0xa2482c80,0x24b1c62c,
+    0xc3a89089,0xb0ea2e42,0x9669a31c,0xa4966a28,
+    0x59a8a269,0x8175e7a,0xb203243,0x718b2892,
+    0x4114105c,0x17597658,0x74ce5d96,0x5c36572d,
+    0xd92d7297,0xe1ce5d70,0xc90c204,0xca2482c8,
+    0x4171c62,0x5d961045,0x976585d6,0x79669533,
+    0x964965a2,0x659689e6,0x308175e7,0x24510510,
+    0x451031c2,0xe2420841,0x5c338714,0x453851c3,
+    0x51c51451,0xc30c31c,0x451450c7,0x41440c20,
+    0xc708914,0x82105144,0xf1c58c90,0x1470ea0d,
+    0x61861863,0x8a1e85e8,0x8687a8a2,0x3081861,
+    0x24853c51,0x5053c368,0x1341144f,0x96194ce5,
+    0x1544d439,0x94385514,0xe0d90d96,0x5415464,
+    0x4f1440c2,0xf0da0921,0x4513d414,0x533944d0,
+    0x350e6586,0x86082181,0xe89e981d,0x18277689,
+    0x10308182,0x89207185,0x41c718b2,0x14e24224,
+    0xc35cb287,0xe1c73871,0x28e1c514,0xc70c32cb,
+    0x204e1c75,0x1c61440c,0xc62ca248,0x90891071,
+    0x2e41c58c,0xa31c70ea,0xe86175e7,0xa269a475,
+    0x5e7a57a8,0x51030817,0x28920718,0xf38718b,
+    0xe5134114,0x39961758,0xe1ce4ce,0x728e3855,
+    0x5ce0d92d,0xc204e1ce,0x81c61440,0x1c62ca24,
+    0xd04503ce,0x85d63944,0x75338e65,0x5d86075e,
+    0x89e69647,0x75e76576,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs5 = /*3 bits per value */ new Int32Array([
+    0x10000000,0xc00000,0x60061,0x400,
+    0x0,0x60000008,0x6b003080,0xdb6ab6db,
+    0x2db6,0x800400,0x49245240,0x11482412,
+    0x104904,0x40020000,0x92292000,0xa4b25924,
+    0x9649658,0xd80c000,0xdb0c001b,0x80db6d86,
+    0x6db01b6d,0xc0600003,0x86000d86,0x6db6c36d,
+    0xddadb6ed,0x300001b6,0x6c360,0xe37236e4,
+    0x46db6236,0xdb6c,0x361b018,0xb91b7200,
+    0x6dbb1b71,0x6db763,0x20100820,0x61248001,
+    0x92492490,0x24820004,0x8041000,0x92400090,
+    0x24924830,0x555b6a49,0x2080012,0x20004804,
+    0x49252449,0x84112492,0x4000928,0x240201,
+    0x92922490,0x58924924,0x49456,0x120d8082,
+    0x6da4800,0x69249249,0x249a01b,0x6c04100,
+    0x6d240009,0x92492483,0x24d5adb4,0x60208001,
+    0x92000483,0x24925236,0x6846da49,0x10400092,
+    0x241b0,0x49291b49,0x636d2492,0x92494935,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,
+]);
+
+class Lev1TParametricDescription extends ParametricDescription {
+    /**
+     * @param {number} absState
+     * @param {number} position
+     * @param {number} vector
+     * @returns {number}
+    */
+    transition(absState, position, vector) {
+        let state = Math.floor(absState / (this.w + 1));
+        let offset = absState % (this.w + 1);
+
+        if (position === this.w) {
+            if (state < 2) {
+                const loc = Math.imul(vector, 2) + state;
+                offset += this.unpack(this.offsetIncrs0, loc, 1);
+                state = this.unpack(this.toStates0, loc, 2) - 1;
+            }
+        } else if (position === this.w - 1) {
+            if (state < 3) {
+                const loc = Math.imul(vector, 3) + state;
+                offset += this.unpack(this.offsetIncrs1, loc, 1);
+                state = this.unpack(this.toStates1, loc, 2) - 1;
+            }
+        } else if (position === this.w - 2) {
+            if (state < 6) {
+                const loc = Math.imul(vector, 6) + state;
+                offset += this.unpack(this.offsetIncrs2, loc, 2);
+                state = this.unpack(this.toStates2, loc, 3) - 1;
+            }
+        } else {
+            // eslint-disable-next-line no-lonely-if
+            if (state < 6) {
+                const loc = Math.imul(vector, 6) + state;
+                offset += this.unpack(this.offsetIncrs3, loc, 2);
+                state = this.unpack(this.toStates3, loc, 3) - 1;
+            }
+        }
+
+        if (state === -1) {
+            // null state
+            return -1;
+        } else {
+            // translate back to abs
+            return Math.imul(state, this.w + 1) + offset;
+        }
+    }
+
+    // state map
+    //   0 -> [(0, 0)]
+    //   1 -> [(0, 1)]
+    //   2 -> [(0, 1), (1, 1)]
+    //   3 -> [(0, 1), (1, 1), (2, 1)]
+    //   4 -> [(0, 1), (2, 1)]
+    //   5 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
+
+
+    /** @param {number} w - length of word being checked */
+    constructor(w) {
+        super(w, 1, new Int32Array([0,1,0,-1,-1,-1]));
+    }
+}
+
+Lev1TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
+    0x2,
+]);
+Lev1TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
+    0x0,
+]);
+
+Lev1TParametricDescription.prototype.toStates1 = /*2 bits per value */ new Int32Array([
+    0xa43,
+]);
+Lev1TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
+    0x38,
+]);
+
+Lev1TParametricDescription.prototype.toStates2 = /*3 bits per value */ new Int32Array([
+    0x12180003,0xb45a4914,0x69,
+]);
+Lev1TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
+    0x558a0000,0x5555,
+]);
+
+Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32Array([
+    0x900c0003,0xa1904864,0x45a49169,0x5a6d196a,
+    0x9634,
+]);
+Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
+    0xa0fc0000,0x5555ba08,0x55555555,
+]);
diff --git a/src/librustdoc/html/static/js/tsconfig.json b/src/librustdoc/html/static/js/tsconfig.json
index b81099bb9df..42993cb0f2a 100644
--- a/src/librustdoc/html/static/js/tsconfig.json
+++ b/src/librustdoc/html/static/js/tsconfig.json
@@ -10,6 +10,6 @@
     "skipLibCheck": true
   },
   "typeAcquisition": {
-    "include": ["./rustdoc.d.ts"]
+    "include": ["./rustdoc.d.ts", "./stringdex.d.ts"]
   }
 }
diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs
index 45589a37069..e670c2f39e7 100644
--- a/src/librustdoc/html/static_files.rs
+++ b/src/librustdoc/html/static_files.rs
@@ -80,6 +80,7 @@ static_files! {
     normalize_css => "static/css/normalize.css",
     main_js => "static/js/main.js",
     search_js => "static/js/search.js",
+    stringdex_js => "static/js/stringdex.js",
     settings_js => "static/js/settings.js",
     src_script_js => "static/js/src-script.js",
     storage_js => "static/js/storage.js",
diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html
index b5d3367a6a1..171e079ed13 100644
--- a/src/librustdoc/html/templates/item_union.html
+++ b/src/librustdoc/html/templates/item_union.html
@@ -1,5 +1,4 @@
 <pre class="rust item-decl"><code>
-    {{ self.render_attributes_in_pre()|safe }}
     {{ self.render_union()|safe }}
 </code></pre>
 {% if !self.is_type_alias %}
@@ -13,7 +12,7 @@
         {% let name = field.name.expect("union field name") %}
         <span id="structfield.{{ name }}" class="{{ ItemType::StructField +}} section-header"> {# #}
             <a href="#structfield.{{ name }}" class="anchor field">§</a> {# #}
-            <code>{{ name }}: {{+ self.print_ty(ty)|safe }}</code> {# #}
+            <code>{{+ self.print_field_attrs(field)|safe }}{{ name }}: {{+ self.print_ty(ty)|safe }}</code> {# #}
         </span>
         {% if let Some(stability_class) = self.stability_field(field) %}
             <span class="stab {{ stability_class }}"></span>
diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html
index 398436e3fe1..1f8ec9f30c5 100644
--- a/src/librustdoc/html/templates/page.html
+++ b/src/librustdoc/html/templates/page.html
@@ -29,6 +29,7 @@
          data-rustdoc-version="{{rustdoc_version}}" {#+ #}
          data-channel="{{rust_channel}}" {#+ #}
          data-search-js="{{files.search_js}}" {#+ #}
+         data-stringdex-js="{{files.stringdex_js}}" {#+ #}
          data-settings-js="{{files.settings_js}}" {#+ #}
     > {# #}
     <script src="{{static_root_path|safe}}{{files.storage_js}}"></script>
@@ -72,18 +73,9 @@
     <![endif]-->
     {{ layout.external_html.before_content|safe }}
     {% if page.css_class != "src" %}
-    <nav class="mobile-topbar"> {# #}
-        <button class="sidebar-menu-toggle" title="show sidebar"></button>
-        {% if !layout.logo.is_empty() || page.rust_logo %}
-        <a class="logo-container" href="{{page.root_path|safe}}{{display_krate_with_trailing_slash|safe}}index.html">
-        {% if page.rust_logo %}
-            <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="">
-        {% else if !layout.logo.is_empty() %}
-            <img src="{{layout.logo}}" alt="">
-        {% endif %}
-        </a>
-        {% endif %}
-    </nav>
+    <rustdoc-topbar> {# #}
+        <h2><a href="#">{{page.short_title}}</a></h2> {# #}
+    </rustdoc-topbar>
     {% endif %}
     <nav class="sidebar">
         {% if page.css_class != "src" %}
@@ -117,9 +109,6 @@
     <div class="sidebar-resizer" title="Drag to resize sidebar"></div> {# #}
     <main>
         {% if page.css_class != "src" %}<div class="width-limiter">{% endif %}
-            {# defined in storage.js to avoid duplicating complex UI across every page #}
-            {# and because the search form only works if JS is enabled anyway #}
-            <rustdoc-search></rustdoc-search> {# #}
             <section id="main-content" class="content">{{ content|safe }}</section>
         {% if page.css_class != "src" %}</div>{% endif %}
     </main>
diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html
index 62954dbb023..640fd3dfee4 100644
--- a/src/librustdoc/html/templates/print_item.html
+++ b/src/librustdoc/html/templates/print_item.html
@@ -12,8 +12,8 @@
     <h1>
         {{typ}}
         <span{% if item_type != "mod" +%} class="{{item_type}}"{% endif %}>
-        {{name}}
-        </span> {# #}
+        {{name|wrapped|safe}}
+        </span>&nbsp;{# #}
         <button id="copy-path" title="Copy item path to clipboard"> {# #}
             Copy item path {# #}
         </button> {# #}
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index f966d926562..5fab8ad2a4b 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -930,7 +930,7 @@ fn maybe_from_hir_attr(
         ),
         AK::ExportName { name, span: _ } => Attribute::ExportName(name.to_string()),
         AK::LinkSection { name, span: _ } => Attribute::LinkSection(name.to_string()),
-        AK::TargetFeature(features, _span) => Attribute::TargetFeature {
+        AK::TargetFeature { features, .. } => Attribute::TargetFeature {
             enable: features.iter().map(|(feat, _span)| feat.to_string()).collect(),
         },
 
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 760e48baffa..b724d7e866a 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -175,15 +175,8 @@ fn target(sess: &rustc_session::Session) -> types::Target {
     }
 }
 
-impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
-    fn descr() -> &'static str {
-        "json"
-    }
-
-    const RUN_ON_MODULE: bool = false;
-    type ModuleData = ();
-
-    fn init(
+impl<'tcx> JsonRenderer<'tcx> {
+    pub(crate) fn init(
         krate: clean::Crate,
         options: RenderOptions,
         cache: Cache,
@@ -205,6 +198,15 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
             krate,
         ))
     }
+}
+
+impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
+    fn descr() -> &'static str {
+        "json"
+    }
+
+    const RUN_ON_MODULE: bool = false;
+    type ModuleData = ();
 
     fn save_module_data(&mut self) -> Self::ModuleData {
         unreachable!("RUN_ON_MODULE = false, should never call save_module_data")
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 28dbd8ba7d3..d891d1fba25 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -80,6 +80,8 @@ use rustc_session::{EarlyDiagCtxt, getopts};
 use tracing::info;
 
 use crate::clean::utils::DOC_RUST_LANG_ORG_VERSION;
+use crate::error::Error;
+use crate::formats::cache::Cache;
 
 /// A macro to create a FxHashMap.
 ///
@@ -663,6 +665,14 @@ fn opts() -> Vec<RustcOptGroup> {
             "disable the minification of CSS/JS files (perma-unstable, do not use with cached files)",
             "",
         ),
+        opt(
+            Unstable,
+            Flag,
+            "",
+            "generate-macro-expansion",
+            "Add possibility to expand macros in the HTML source code pages",
+            "",
+        ),
         // deprecated / removed options
         opt(
             Stable,
@@ -726,13 +736,23 @@ pub(crate) fn wrap_return(dcx: DiagCtxtHandle<'_>, res: Result<(), String>) {
     }
 }
 
-fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
+fn run_renderer<
+    'tcx,
+    T: formats::FormatRenderer<'tcx>,
+    F: FnOnce(
+        clean::Crate,
+        config::RenderOptions,
+        Cache,
+        TyCtxt<'tcx>,
+    ) -> Result<(T, clean::Crate), Error>,
+>(
     krate: clean::Crate,
     renderopts: config::RenderOptions,
     cache: formats::cache::Cache,
     tcx: TyCtxt<'tcx>,
+    init: F,
 ) {
-    match formats::run_format::<T>(krate, renderopts, cache, tcx) {
+    match formats::run_format::<T, F>(krate, renderopts, cache, tcx, init) {
         Ok(_) => tcx.dcx().abort_if_errors(),
         Err(e) => {
             let mut msg =
@@ -862,6 +882,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
     let scrape_examples_options = options.scrape_examples_options.clone();
     let bin_crate = options.bin_crate;
 
+    let output_format = options.output_format;
     let config = core::create_config(input, options, &render_options);
 
     let registered_lints = config.register_lints.is_some();
@@ -886,9 +907,10 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
                 sess.dcx().fatal("Compilation failed, aborting rustdoc");
             }
 
-            let (krate, render_opts, mut cache) = sess.time("run_global_ctxt", || {
-                core::run_global_ctxt(tcx, show_coverage, render_options, output_format)
-            });
+            let (krate, render_opts, mut cache, expanded_macros) = sess
+                .time("run_global_ctxt", || {
+                    core::run_global_ctxt(tcx, show_coverage, render_options, output_format)
+                });
             info!("finished with rustc");
 
             if let Some(options) = scrape_examples_options {
@@ -919,10 +941,24 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
             info!("going to format");
             match output_format {
                 config::OutputFormat::Html => sess.time("render_html", || {
-                    run_renderer::<html::render::Context<'_>>(krate, render_opts, cache, tcx)
+                    run_renderer(
+                        krate,
+                        render_opts,
+                        cache,
+                        tcx,
+                        |krate, render_opts, cache, tcx| {
+                            html::render::Context::init(
+                                krate,
+                                render_opts,
+                                cache,
+                                tcx,
+                                expanded_macros,
+                            )
+                        },
+                    )
                 }),
                 config::OutputFormat::Json => sess.time("render_json", || {
-                    run_renderer::<json::JsonRenderer<'_>>(krate, render_opts, cache, tcx)
+                    run_renderer(krate, render_opts, cache, tcx, json::JsonRenderer::init)
                 }),
                 // Already handled above with doctest runners.
                 config::OutputFormat::Doctest => unreachable!(),
diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs
index 9f71d6ae789..16034c11827 100644
--- a/src/librustdoc/scrape_examples.rs
+++ b/src/librustdoc/scrape_examples.rs
@@ -18,7 +18,6 @@ use rustc_span::edition::Edition;
 use rustc_span::{BytePos, FileName, SourceFile};
 use tracing::{debug, trace, warn};
 
-use crate::formats::renderer::FormatRenderer;
 use crate::html::render::Context;
 use crate::{clean, config, formats};
 
@@ -276,7 +275,8 @@ pub(crate) fn run(
     let inner = move || -> Result<(), String> {
         // Generates source files for examples
         renderopts.no_emit_shared = true;
-        let (cx, _) = Context::init(krate, renderopts, cache, tcx).map_err(|e| e.to_string())?;
+        let (cx, _) = Context::init(krate, renderopts, cache, tcx, Default::default())
+            .map_err(|e| e.to_string())?;
 
         // Collect CrateIds corresponding to provided target crates
         // If two different versions of the crate in the dependency tree, then examples will be
diff --git a/src/tools/build-manifest/Cargo.toml b/src/tools/build-manifest/Cargo.toml
index 7e0c4bee2b3..efa99f181b3 100644
--- a/src/tools/build-manifest/Cargo.toml
+++ b/src/tools/build-manifest/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
-toml = "0.5"
+toml = "0.7"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 anyhow = "1.0.32"
diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs
index 0520eff0fa2..8f88ab10bf7 100644
--- a/src/tools/build-manifest/src/main.rs
+++ b/src/tools/build-manifest/src/main.rs
@@ -205,7 +205,7 @@ static TARGETS: &[&str] = &[
 ///
 /// The order here matters, more specific entries should be first.
 static DOCS_FALLBACK: &[(&str, &str)] = &[
-    ("-apple-", "x86_64-apple-darwin"),
+    ("-apple-", "aarch64-apple-darwin"),
     ("aarch64", "aarch64-unknown-linux-gnu"),
     ("arm-", "aarch64-unknown-linux-gnu"),
     ("", "x86_64-unknown-linux-gnu"),
diff --git a/src/tools/bump-stage0/Cargo.toml b/src/tools/bump-stage0/Cargo.toml
index 6ee7a831839..b7f3625da91 100644
--- a/src/tools/bump-stage0/Cargo.toml
+++ b/src/tools/bump-stage0/Cargo.toml
@@ -11,4 +11,4 @@ build_helper = { path = "../../build_helper" }
 curl = "0.4.38"
 indexmap = { version = "2.0.0", features = ["serde"] }
 serde = { version = "1.0.125", features = ["derive"] }
-toml = "0.5.7"
+toml = "0.7"
diff --git a/src/tools/cargo b/src/tools/cargo
-Subproject 840b83a10fb0e039a83f4d70ad032892c287570
+Subproject 623d536836b4cde09ce38609232a024d5b25da8
diff --git a/src/tools/clippy/.github/workflows/clippy_dev.yml b/src/tools/clippy/.github/workflows/clippy_dev.yml
index d6534fbaff9..d530eb6c73a 100644
--- a/src/tools/clippy/.github/workflows/clippy_dev.yml
+++ b/src/tools/clippy/.github/workflows/clippy_dev.yml
@@ -16,7 +16,7 @@ jobs:
     steps:
     # Setup
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         # Unsetting this would make so that any malicious package could get our Github Token
         persist-credentials: false
diff --git a/src/tools/clippy/.github/workflows/clippy_mq.yml b/src/tools/clippy/.github/workflows/clippy_mq.yml
index 07d5a08304e..0bcb7135935 100644
--- a/src/tools/clippy/.github/workflows/clippy_mq.yml
+++ b/src/tools/clippy/.github/workflows/clippy_mq.yml
@@ -36,7 +36,7 @@ jobs:
     steps:
     # Setup
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         persist-credentials: false
 
@@ -96,7 +96,7 @@ jobs:
     steps:
      # Setup
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         persist-credentials: false
 
@@ -114,7 +114,7 @@ jobs:
     steps:
     # Setup
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         persist-credentials: false
 
@@ -170,7 +170,7 @@ jobs:
     steps:
     # Setup
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         persist-credentials: false
 
diff --git a/src/tools/clippy/.github/workflows/clippy_pr.yml b/src/tools/clippy/.github/workflows/clippy_pr.yml
index 880ebd6e5d5..d91c638a8fb 100644
--- a/src/tools/clippy/.github/workflows/clippy_pr.yml
+++ b/src/tools/clippy/.github/workflows/clippy_pr.yml
@@ -24,7 +24,7 @@ jobs:
     steps:
     # Setup
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         # Unsetting this would make so that any malicious package could get our Github Token
         persist-credentials: false
diff --git a/src/tools/clippy/.github/workflows/deploy.yml b/src/tools/clippy/.github/workflows/deploy.yml
index ede19c11257..48c5bd36dbc 100644
--- a/src/tools/clippy/.github/workflows/deploy.yml
+++ b/src/tools/clippy/.github/workflows/deploy.yml
@@ -25,13 +25,13 @@ jobs:
     steps:
     # Setup
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         # Unsetting this would make so that any malicious package could get our Github Token
         persist-credentials: false
 
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         ref: ${{ env.TARGET_BRANCH }}
         path: 'out'
diff --git a/src/tools/clippy/.github/workflows/feature_freeze.yml b/src/tools/clippy/.github/workflows/feature_freeze.yml
index ec59be3e7f6..5b139e76700 100644
--- a/src/tools/clippy/.github/workflows/feature_freeze.yml
+++ b/src/tools/clippy/.github/workflows/feature_freeze.yml
@@ -16,7 +16,7 @@ jobs:
     permissions:
       pull-requests: write
 
-    # Do not in any case add code that runs anything coming from the  the content
+    # Do not in any case add code that runs anything coming from the content
     # of the pull request, as malicious code would be able to access the private
     # GitHub token.
     steps:
diff --git a/src/tools/clippy/.github/workflows/lintcheck.yml b/src/tools/clippy/.github/workflows/lintcheck.yml
index 003d0395739..390d6a0f747 100644
--- a/src/tools/clippy/.github/workflows/lintcheck.yml
+++ b/src/tools/clippy/.github/workflows/lintcheck.yml
@@ -24,7 +24,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         fetch-depth: 2
         # Unsetting this would make so that any malicious package could get our Github Token
@@ -80,7 +80,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         # Unsetting this would make so that any malicious package could get our Github Token
         persist-credentials: false
@@ -113,7 +113,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         # Unsetting this would make so that any malicious package could get our Github Token
         persist-credentials: false
diff --git a/src/tools/clippy/.github/workflows/remark.yml b/src/tools/clippy/.github/workflows/remark.yml
index 7e7e26818c0..c9d350ee0b3 100644
--- a/src/tools/clippy/.github/workflows/remark.yml
+++ b/src/tools/clippy/.github/workflows/remark.yml
@@ -11,7 +11,7 @@ jobs:
     steps:
     # Setup
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         # Unsetting this would make so that any malicious package could get our Github Token
         persist-credentials: false
diff --git a/src/tools/clippy/CONTRIBUTING.md b/src/tools/clippy/CONTRIBUTING.md
index 42ed624ec21..f7f0a1ce249 100644
--- a/src/tools/clippy/CONTRIBUTING.md
+++ b/src/tools/clippy/CONTRIBUTING.md
@@ -17,7 +17,7 @@ All contributors are expected to follow the [Rust Code of Conduct].
   - [High level approach](#high-level-approach)
   - [Finding something to fix/improve](#finding-something-to-fiximprove)
   - [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work)
-    - [IntelliJ Rust](#intellij-rust)
+    - [RustRover](#rustrover)
     - [Rust Analyzer](#rust-analyzer)
   - [How Clippy works](#how-clippy-works)
   - [Issue and PR triage](#issue-and-pr-triage)
@@ -92,22 +92,22 @@ an AST expression).
 
 ## Getting code-completion for rustc internals to work
 
-### IntelliJ Rust
-Unfortunately, [`IntelliJ Rust`][IntelliJ_rust_homepage] does not (yet?) understand how Clippy uses compiler-internals
+### RustRover
+Unfortunately, [`RustRover`][RustRover_homepage] does not (yet?) understand how Clippy uses compiler-internals
 using `extern crate` and it also needs to be able to read the source files of the rustc-compiler which are not
 available via a `rustup` component at the time of writing.
 To work around this, you need to have a copy of the [rustc-repo][rustc_repo] available which can be obtained via
 `git clone https://github.com/rust-lang/rust/`.
 Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies
-which `IntelliJ Rust` will be able to understand.
+which `RustRover` will be able to understand.
 Run `cargo dev setup intellij --repo-path <repo-path>` where `<repo-path>` is a path to the rustc repo
 you just cloned.
 The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to
-Clippy's `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses.
+Clippy's `Cargo.toml`s and should allow `RustRover` to understand most of the types that Clippy uses.
 Just make sure to remove the dependencies again before finally making a pull request!
 
 [rustc_repo]: https://github.com/rust-lang/rust/
-[IntelliJ_rust_homepage]: https://intellij-rust.github.io/
+[RustRover_homepage]: https://www.jetbrains.com/rust/
 
 ### Rust Analyzer
 For [`rust-analyzer`][ra_homepage] to work correctly make sure that in the `rust-analyzer` configuration you set
diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml
index daf1c98cdc9..b3618932ded 100644
--- a/src/tools/clippy/Cargo.toml
+++ b/src/tools/clippy/Cargo.toml
@@ -64,3 +64,7 @@ harness = false
 [[test]]
 name = "dogfood"
 harness = false
+
+[lints.rust.unexpected_cfgs]
+level = "warn"
+check-cfg = ['cfg(bootstrap)']
diff --git a/src/tools/clippy/book/src/continuous_integration/github_actions.md b/src/tools/clippy/book/src/continuous_integration/github_actions.md
index b588c8f0f02..62d32446d92 100644
--- a/src/tools/clippy/book/src/continuous_integration/github_actions.md
+++ b/src/tools/clippy/book/src/continuous_integration/github_actions.md
@@ -15,7 +15,7 @@ jobs:
   clippy_check:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Run Clippy
         run: cargo clippy --all-targets --all-features
 ```
diff --git a/src/tools/clippy/book/src/development/basics.md b/src/tools/clippy/book/src/development/basics.md
index fc405249bcf..19f626ab804 100644
--- a/src/tools/clippy/book/src/development/basics.md
+++ b/src/tools/clippy/book/src/development/basics.md
@@ -95,7 +95,7 @@ cargo dev new_lint
 cargo dev deprecate
 # automatically formatting all code before each commit
 cargo dev setup git-hook
-# (experimental) Setup Clippy to work with IntelliJ-Rust
+# (experimental) Setup Clippy to work with RustRover
 cargo dev setup intellij
 # runs the `dogfood` tests
 cargo dev dogfood
@@ -103,7 +103,7 @@ cargo dev dogfood
 
 More about [intellij] command usage and reasons.
 
-[intellij]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#intellij-rust
+[intellij]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#rustrover
 
 ## lintcheck
 
diff --git a/src/tools/clippy/book/src/development/common_tools_writing_lints.md b/src/tools/clippy/book/src/development/common_tools_writing_lints.md
index e23b32039c9..3bec3ce33af 100644
--- a/src/tools/clippy/book/src/development/common_tools_writing_lints.md
+++ b/src/tools/clippy/book/src/development/common_tools_writing_lints.md
@@ -141,7 +141,7 @@ impl LateLintPass<'_> for MyStructLint {
             // we are looking for the `DefId` of `Drop` trait in lang items
             .drop_trait()
             // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
-            .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
+            .is_some_and(|id| implements_trait(cx, ty, id, &[])) {
                 // `expr` implements `Drop` trait
             }
     }
diff --git a/src/tools/clippy/book/src/lint_configuration.md b/src/tools/clippy/book/src/lint_configuration.md
index 7f16f3a9810..05590ff7b1c 100644
--- a/src/tools/clippy/book/src/lint_configuration.md
+++ b/src/tools/clippy/book/src/lint_configuration.md
@@ -555,7 +555,7 @@ default configuration of Clippy. By default, any configuration will replace the
 * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
 * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
 
-**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
+**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
 
 ---
 **Affected lints:**
diff --git a/src/tools/clippy/clippy_config/src/conf.rs b/src/tools/clippy/clippy_config/src/conf.rs
index 8167d75583e..2ad3f2efcdd 100644
--- a/src/tools/clippy/clippy_config/src/conf.rs
+++ b/src/tools/clippy/clippy_config/src/conf.rs
@@ -34,7 +34,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
     "GitHub", "GitLab",
     "IPv4", "IPv6",
     "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript",
-    "WebAssembly",
+    "PowerPC", "WebAssembly",
     "NaN", "NaNs",
     "OAuth", "GraphQL",
     "OCaml",
diff --git a/src/tools/clippy/clippy_dev/src/dogfood.rs b/src/tools/clippy/clippy_dev/src/dogfood.rs
index 7e9d92458d0..d0fca952b93 100644
--- a/src/tools/clippy/clippy_dev/src/dogfood.rs
+++ b/src/tools/clippy/clippy_dev/src/dogfood.rs
@@ -1,35 +1,28 @@
-use crate::utils::exit_if_err;
-use std::process::Command;
+use crate::utils::{cargo_cmd, run_exit_on_err};
+use itertools::Itertools;
 
 /// # Panics
 ///
 /// Panics if unable to run the dogfood test
 #[allow(clippy::fn_params_excessive_bools)]
 pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool, allow_no_vcs: bool) {
-    let mut cmd = Command::new("cargo");
-
-    cmd.args(["test", "--test", "dogfood"])
-        .args(["--features", "internal"])
-        .args(["--", "dogfood_clippy", "--nocapture"]);
-
-    let mut dogfood_args = Vec::new();
-    if fix {
-        dogfood_args.push("--fix");
-    }
-
-    if allow_dirty {
-        dogfood_args.push("--allow-dirty");
-    }
-
-    if allow_staged {
-        dogfood_args.push("--allow-staged");
-    }
-
-    if allow_no_vcs {
-        dogfood_args.push("--allow-no-vcs");
-    }
-
-    cmd.env("__CLIPPY_DOGFOOD_ARGS", dogfood_args.join(" "));
-
-    exit_if_err(cmd.status());
+    run_exit_on_err(
+        "cargo test",
+        cargo_cmd()
+            .args(["test", "--test", "dogfood"])
+            .args(["--features", "internal"])
+            .args(["--", "dogfood_clippy", "--nocapture"])
+            .env(
+                "__CLIPPY_DOGFOOD_ARGS",
+                [
+                    if fix { "--fix" } else { "" },
+                    if allow_dirty { "--allow-dirty" } else { "" },
+                    if allow_staged { "--allow-staged" } else { "" },
+                    if allow_no_vcs { "--allow-no-vcs" } else { "" },
+                ]
+                .iter()
+                .filter(|x| !x.is_empty())
+                .join(" "),
+            ),
+    );
 }
diff --git a/src/tools/clippy/clippy_dev/src/lib.rs b/src/tools/clippy/clippy_dev/src/lib.rs
index 40aadf4589a..16f413e0c86 100644
--- a/src/tools/clippy/clippy_dev/src/lib.rs
+++ b/src/tools/clippy/clippy_dev/src/lib.rs
@@ -15,8 +15,7 @@
 )]
 #![allow(clippy::missing_panics_doc)]
 
-// The `rustc_driver` crate seems to be required in order to use the `rust_lexer` crate.
-#[allow(unused_extern_crates)]
+#[expect(unused_extern_crates, reason = "required to link to rustc crates")]
 extern crate rustc_driver;
 extern crate rustc_lexer;
 extern crate rustc_literal_escaper;
@@ -32,4 +31,6 @@ pub mod serve;
 pub mod setup;
 pub mod sync;
 pub mod update_lints;
-pub mod utils;
+
+mod utils;
+pub use utils::{ClippyInfo, UpdateMode};
diff --git a/src/tools/clippy/clippy_dev/src/lint.rs b/src/tools/clippy/clippy_dev/src/lint.rs
index 0d66f167a38..2d9f563cdae 100644
--- a/src/tools/clippy/clippy_dev/src/lint.rs
+++ b/src/tools/clippy/clippy_dev/src/lint.rs
@@ -1,19 +1,18 @@
-use crate::utils::{cargo_clippy_path, exit_if_err};
-use std::process::{self, Command};
+use crate::utils::{ErrAction, cargo_cmd, expect_action, run_exit_on_err};
+use std::process::Command;
 use std::{env, fs};
 
-pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>) {
-    let is_file = match fs::metadata(path) {
-        Ok(metadata) => metadata.is_file(),
-        Err(e) => {
-            eprintln!("Failed to read {path}: {e:?}");
-            process::exit(1);
-        },
-    };
+#[cfg(not(windows))]
+static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
+#[cfg(windows)]
+static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
 
+pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>) {
+    let is_file = expect_action(fs::metadata(path), ErrAction::Read, path).is_file();
     if is_file {
-        exit_if_err(
-            Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
+        run_exit_on_err(
+            "cargo run",
+            cargo_cmd()
                 .args(["run", "--bin", "clippy-driver", "--"])
                 .args(["-L", "./target/debug"])
                 .args(["-Z", "no-codegen"])
@@ -21,24 +20,25 @@ pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>
                 .arg(path)
                 .args(args)
                 // Prevent rustc from creating `rustc-ice-*` files the console output is enough.
-                .env("RUSTC_ICE", "0")
-                .status(),
+                .env("RUSTC_ICE", "0"),
         );
     } else {
-        exit_if_err(
-            Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
-                .arg("build")
-                .status(),
-        );
-
-        let status = Command::new(cargo_clippy_path())
-            .arg("clippy")
-            .args(args)
-            // Prevent rustc from creating `rustc-ice-*` files the console output is enough.
-            .env("RUSTC_ICE", "0")
-            .current_dir(path)
-            .status();
+        // Ideally this would just be `cargo run`, but the working directory needs to be
+        // set to clippy's directory when building, and the target project's directory
+        // when running clippy. `cargo` can only set a single working directory for both
+        // when using `run`.
+        run_exit_on_err("cargo build", cargo_cmd().arg("build"));
 
-        exit_if_err(status);
+        let mut exe = env::current_exe().expect("failed to get current executable name");
+        exe.set_file_name(CARGO_CLIPPY_EXE);
+        run_exit_on_err(
+            "cargo clippy",
+            Command::new(exe)
+                .arg("clippy")
+                .args(args)
+                // Prevent rustc from creating `rustc-ice-*` files the console output is enough.
+                .env("RUSTC_ICE", "0")
+                .current_dir(path),
+        );
     }
 }
diff --git a/src/tools/clippy/clippy_dev/src/main.rs b/src/tools/clippy/clippy_dev/src/main.rs
index 26aa269fb63..5fef231f6ca 100644
--- a/src/tools/clippy/clippy_dev/src/main.rs
+++ b/src/tools/clippy/clippy_dev/src/main.rs
@@ -4,14 +4,15 @@
 
 use clap::{Args, Parser, Subcommand};
 use clippy_dev::{
-    deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, update_lints, utils,
+    ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync,
+    update_lints,
 };
 use std::convert::Infallible;
 use std::env;
 
 fn main() {
     let dev = Dev::parse();
-    let clippy = utils::ClippyInfo::search_for_manifest();
+    let clippy = ClippyInfo::search_for_manifest();
     if let Err(e) = env::set_current_dir(&clippy.path) {
         panic!("error setting current directory to `{}`: {e}", clippy.path.display());
     }
@@ -26,8 +27,8 @@ fn main() {
             allow_staged,
             allow_no_vcs,
         } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
-        DevCommand::Fmt { check } => fmt::run(utils::UpdateMode::from_check(check)),
-        DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)),
+        DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)),
+        DevCommand::UpdateLints { check } => update_lints::update(UpdateMode::from_check(check)),
         DevCommand::NewLint {
             pass,
             name,
@@ -35,7 +36,7 @@ fn main() {
             r#type,
             msrv,
         } => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) {
-            Ok(()) => update_lints::update(utils::UpdateMode::Change),
+            Ok(()) => update_lints::update(UpdateMode::Change),
             Err(e) => eprintln!("Unable to create lint: {e}"),
         },
         DevCommand::Setup(SetupCommand { subcommand }) => match subcommand {
diff --git a/src/tools/clippy/clippy_dev/src/serve.rs b/src/tools/clippy/clippy_dev/src/serve.rs
index 498ffeba9d6..d9e01813381 100644
--- a/src/tools/clippy/clippy_dev/src/serve.rs
+++ b/src/tools/clippy/clippy_dev/src/serve.rs
@@ -1,7 +1,11 @@
+use crate::utils::{ErrAction, cargo_cmd, expect_action};
+use core::fmt::Display;
+use core::mem;
 use std::path::Path;
 use std::process::Command;
 use std::time::{Duration, SystemTime};
-use std::{env, thread};
+use std::{fs, thread};
+use walkdir::WalkDir;
 
 #[cfg(windows)]
 const PYTHON: &str = "python";
@@ -18,56 +22,83 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
         Some(lint) => format!("http://localhost:{port}/#{lint}"),
     });
 
+    let mut last_update = mtime("util/gh-pages/index.html");
     loop {
-        let index_time = mtime("util/gh-pages/index.html");
-        let times = [
-            "clippy_lints/src",
-            "util/gh-pages/index_template.html",
-            "tests/compile-test.rs",
-        ]
-        .map(mtime);
-
-        if times.iter().any(|&time| index_time < time) {
-            Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
-                .arg("collect-metadata")
-                .spawn()
-                .unwrap()
-                .wait()
-                .unwrap();
+        if is_metadata_outdated(mem::replace(&mut last_update, SystemTime::now())) {
+            // Ignore the command result; we'll fall back to displaying the old metadata.
+            let _ = expect_action(
+                cargo_cmd().arg("collect-metadata").status(),
+                ErrAction::Run,
+                "cargo collect-metadata",
+            );
+            last_update = SystemTime::now();
         }
+
+        // Only start the web server the first time around.
         if let Some(url) = url.take() {
             thread::spawn(move || {
-                let mut child = Command::new(PYTHON)
-                    .arg("-m")
-                    .arg("http.server")
-                    .arg(port.to_string())
-                    .current_dir("util/gh-pages")
-                    .spawn()
-                    .unwrap();
+                let mut child = expect_action(
+                    Command::new(PYTHON)
+                        .args(["-m", "http.server", port.to_string().as_str()])
+                        .current_dir("util/gh-pages")
+                        .spawn(),
+                    ErrAction::Run,
+                    "python -m http.server",
+                );
                 // Give some time for python to start
                 thread::sleep(Duration::from_millis(500));
                 // Launch browser after first export.py has completed and http.server is up
                 let _result = opener::open(url);
-                child.wait().unwrap();
+                expect_action(child.wait(), ErrAction::Run, "python -m http.server");
             });
         }
+
+        // Delay to avoid updating the metadata too aggressively.
         thread::sleep(Duration::from_millis(1000));
     }
 }
 
-fn mtime(path: impl AsRef<Path>) -> SystemTime {
-    let path = path.as_ref();
-    if path.is_dir() {
-        path.read_dir()
-            .into_iter()
-            .flatten()
-            .flatten()
-            .map(|entry| mtime(entry.path()))
-            .max()
-            .unwrap_or(SystemTime::UNIX_EPOCH)
-    } else {
-        path.metadata()
-            .and_then(|metadata| metadata.modified())
-            .unwrap_or(SystemTime::UNIX_EPOCH)
+fn log_err_and_continue<T>(res: Result<T, impl Display>, path: &Path) -> Option<T> {
+    match res {
+        Ok(x) => Some(x),
+        Err(ref e) => {
+            eprintln!("error reading `{}`: {e}", path.display());
+            None
+        },
     }
 }
+
+fn mtime(path: &str) -> SystemTime {
+    log_err_and_continue(fs::metadata(path), path.as_ref())
+        .and_then(|metadata| log_err_and_continue(metadata.modified(), path.as_ref()))
+        .unwrap_or(SystemTime::UNIX_EPOCH)
+}
+
+fn is_metadata_outdated(time: SystemTime) -> bool {
+    // Ignore all IO errors here. We don't want to stop them from hosting the server.
+    if time < mtime("util/gh-pages/index_template.html") || time < mtime("tests/compile-test.rs") {
+        return true;
+    }
+    let Some(dir) = log_err_and_continue(fs::read_dir("."), ".".as_ref()) else {
+        return false;
+    };
+    dir.map_while(|e| log_err_and_continue(e, ".".as_ref())).any(|e| {
+        let name = e.file_name();
+        let name_bytes = name.as_encoded_bytes();
+        if (name_bytes.starts_with(b"clippy_lints") && name_bytes != b"clippy_lints_internal")
+            || name_bytes == b"clippy_config"
+        {
+            WalkDir::new(&name)
+                .into_iter()
+                .map_while(|e| log_err_and_continue(e, name.as_ref()))
+                .filter(|e| e.file_type().is_file())
+                .filter_map(|e| {
+                    log_err_and_continue(e.metadata(), e.path())
+                        .and_then(|m| log_err_and_continue(m.modified(), e.path()))
+                })
+                .any(|ftime| time < ftime)
+        } else {
+            false
+        }
+    })
+}
diff --git a/src/tools/clippy/clippy_dev/src/setup/git_hook.rs b/src/tools/clippy/clippy_dev/src/setup/git_hook.rs
index c7c53bc69d0..c5a1e8264c7 100644
--- a/src/tools/clippy/clippy_dev/src/setup/git_hook.rs
+++ b/src/tools/clippy/clippy_dev/src/setup/git_hook.rs
@@ -1,8 +1,6 @@
 use std::fs;
 use std::path::Path;
 
-use super::verify_inside_clippy_dir;
-
 /// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo.
 /// I've decided against this for the sake of simplicity and to make sure that it doesn't install
 /// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool
@@ -35,10 +33,6 @@ pub fn install_hook(force_override: bool) {
 }
 
 fn check_precondition(force_override: bool) -> bool {
-    if !verify_inside_clippy_dir() {
-        return false;
-    }
-
     // Make sure that we can find the git repository
     let git_path = Path::new(REPO_GIT_DIR);
     if !git_path.exists() || !git_path.is_dir() {
diff --git a/src/tools/clippy/clippy_dev/src/setup/mod.rs b/src/tools/clippy/clippy_dev/src/setup/mod.rs
index b0d31814639..5e938fff126 100644
--- a/src/tools/clippy/clippy_dev/src/setup/mod.rs
+++ b/src/tools/clippy/clippy_dev/src/setup/mod.rs
@@ -2,23 +2,3 @@ pub mod git_hook;
 pub mod intellij;
 pub mod toolchain;
 pub mod vscode;
-
-use std::path::Path;
-
-const CLIPPY_DEV_DIR: &str = "clippy_dev";
-
-/// This function verifies that the tool is being executed in the clippy directory.
-/// This is useful to ensure that setups only modify Clippy's resources. The verification
-/// is done by checking that `clippy_dev` is a sub directory of the current directory.
-///
-/// It will print an error message and return `false` if the directory could not be
-/// verified.
-fn verify_inside_clippy_dir() -> bool {
-    let path = Path::new(CLIPPY_DEV_DIR);
-    if path.exists() && path.is_dir() {
-        true
-    } else {
-        eprintln!("error: unable to verify that the working directory is clippy's directory");
-        false
-    }
-}
diff --git a/src/tools/clippy/clippy_dev/src/setup/toolchain.rs b/src/tools/clippy/clippy_dev/src/setup/toolchain.rs
index ecd80215f7e..c64ae4ef3c3 100644
--- a/src/tools/clippy/clippy_dev/src/setup/toolchain.rs
+++ b/src/tools/clippy/clippy_dev/src/setup/toolchain.rs
@@ -1,20 +1,12 @@
+use crate::utils::{cargo_cmd, run_exit_on_err};
 use std::env::consts::EXE_SUFFIX;
 use std::env::current_dir;
 use std::ffi::OsStr;
 use std::fs;
 use std::path::{Path, PathBuf};
-use std::process::Command;
 use walkdir::WalkDir;
 
-use crate::utils::exit_if_err;
-
-use super::verify_inside_clippy_dir;
-
 pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
-    if !verify_inside_clippy_dir() {
-        return;
-    }
-
     let rustup_home = std::env::var("RUSTUP_HOME").unwrap();
     let toolchain = std::env::var("RUSTUP_TOOLCHAIN").unwrap();
 
@@ -51,11 +43,10 @@ pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
         }
     }
 
-    let status = Command::new("cargo")
-        .arg("build")
-        .args(release.then_some("--release"))
-        .status();
-    exit_if_err(status);
+    run_exit_on_err(
+        "cargo build",
+        cargo_cmd().arg("build").args(release.then_some("--release")),
+    );
 
     install_bin("cargo-clippy", &dest, standalone, release);
     install_bin("clippy-driver", &dest, standalone, release);
diff --git a/src/tools/clippy/clippy_dev/src/setup/vscode.rs b/src/tools/clippy/clippy_dev/src/setup/vscode.rs
index a37c873eed4..a24aef65991 100644
--- a/src/tools/clippy/clippy_dev/src/setup/vscode.rs
+++ b/src/tools/clippy/clippy_dev/src/setup/vscode.rs
@@ -1,8 +1,6 @@
 use std::fs;
 use std::path::Path;
 
-use super::verify_inside_clippy_dir;
-
 const VSCODE_DIR: &str = ".vscode";
 const TASK_SOURCE_FILE: &str = "util/etc/vscode-tasks.json";
 const TASK_TARGET_FILE: &str = ".vscode/tasks.json";
@@ -22,10 +20,6 @@ pub fn install_tasks(force_override: bool) {
 }
 
 fn check_install_precondition(force_override: bool) -> bool {
-    if !verify_inside_clippy_dir() {
-        return false;
-    }
-
     let vs_dir_path = Path::new(VSCODE_DIR);
     if vs_dir_path.exists() {
         // verify the target will be valid
diff --git a/src/tools/clippy/clippy_dev/src/utils.rs b/src/tools/clippy/clippy_dev/src/utils.rs
index 89962a11034..057951d0e33 100644
--- a/src/tools/clippy/clippy_dev/src/utils.rs
+++ b/src/tools/clippy/clippy_dev/src/utils.rs
@@ -8,15 +8,10 @@ use std::ffi::OsStr;
 use std::fs::{self, OpenOptions};
 use std::io::{self, Read as _, Seek as _, SeekFrom, Write};
 use std::path::{Path, PathBuf};
-use std::process::{self, Command, ExitStatus, Stdio};
+use std::process::{self, Command, Stdio};
 use std::{env, thread};
 use walkdir::WalkDir;
 
-#[cfg(not(windows))]
-static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
-#[cfg(windows)]
-static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
-
 #[derive(Clone, Copy)]
 pub enum ErrAction {
     Open,
@@ -118,16 +113,14 @@ impl<'a> File<'a> {
     }
 }
 
-/// Returns the path to the `cargo-clippy` binary
-///
-/// # Panics
-///
-/// Panics if the path of current executable could not be retrieved.
+/// Creates a `Command` for running cargo.
 #[must_use]
-pub fn cargo_clippy_path() -> PathBuf {
-    let mut path = env::current_exe().expect("failed to get current executable name");
-    path.set_file_name(CARGO_CLIPPY_EXE);
-    path
+pub fn cargo_cmd() -> Command {
+    if let Some(path) = env::var_os("CARGO") {
+        Command::new(path)
+    } else {
+        Command::new("cargo")
+    }
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -288,19 +281,6 @@ impl ClippyInfo {
     }
 }
 
-/// # Panics
-/// Panics if given command result was failed.
-pub fn exit_if_err(status: io::Result<ExitStatus>) {
-    match status.expect("failed to run command").code() {
-        Some(0) => {},
-        Some(n) => process::exit(n),
-        None => {
-            eprintln!("Killed by signal");
-            process::exit(1);
-        },
-    }
-}
-
 #[derive(Clone, Copy)]
 pub enum UpdateStatus {
     Unchanged,
@@ -341,6 +321,7 @@ pub struct FileUpdater {
     dst_buf: String,
 }
 impl FileUpdater {
+    #[track_caller]
     fn update_file_checked_inner(
         &mut self,
         tool: &str,
@@ -364,6 +345,7 @@ impl FileUpdater {
         }
     }
 
+    #[track_caller]
     fn update_file_inner(&mut self, path: &Path, update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus) {
         let mut file = File::open(path, OpenOptions::new().read(true).write(true));
         file.read_to_cleared_string(&mut self.src_buf);
@@ -373,6 +355,7 @@ impl FileUpdater {
         }
     }
 
+    #[track_caller]
     pub fn update_file_checked(
         &mut self,
         tool: &str,
@@ -383,6 +366,7 @@ impl FileUpdater {
         self.update_file_checked_inner(tool, mode, path.as_ref(), update);
     }
 
+    #[track_caller]
     pub fn update_file(
         &mut self,
         path: impl AsRef<Path>,
@@ -450,7 +434,6 @@ pub enum Token<'a> {
     OpenParen,
     Pound,
     Semi,
-    Slash,
 }
 
 pub struct RustSearcher<'txt> {
@@ -528,7 +511,6 @@ impl<'txt> RustSearcher<'txt> {
                 | (Token::OpenParen, lexer::TokenKind::OpenParen)
                 | (Token::Pound, lexer::TokenKind::Pound)
                 | (Token::Semi, lexer::TokenKind::Semi)
-                | (Token::Slash, lexer::TokenKind::Slash)
                 | (
                     Token::LitStr,
                     lexer::TokenKind::Literal {
@@ -601,7 +583,7 @@ impl<'txt> RustSearcher<'txt> {
     }
 }
 
-#[expect(clippy::must_use_candidate)]
+#[track_caller]
 pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
     match OpenOptions::new().create_new(true).write(true).open(new_name) {
         Ok(file) => drop(file),
@@ -623,7 +605,7 @@ pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
     }
 }
 
-#[expect(clippy::must_use_candidate)]
+#[track_caller]
 pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
     match fs::create_dir(new_name) {
         Ok(()) => {},
@@ -649,10 +631,19 @@ pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
     }
 }
 
-pub fn write_file(path: &Path, contents: &str) {
-    expect_action(fs::write(path, contents), ErrAction::Write, path);
+#[track_caller]
+pub fn run_exit_on_err(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) {
+    match expect_action(cmd.status(), ErrAction::Run, path.as_ref()).code() {
+        Some(0) => {},
+        Some(n) => process::exit(n),
+        None => {
+            eprintln!("{} killed by signal", path.as_ref().display());
+            process::exit(1);
+        },
+    }
 }
 
+#[track_caller]
 #[must_use]
 pub fn run_with_output(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) -> Vec<u8> {
     fn f(path: &Path, cmd: &mut Command) -> Vec<u8> {
@@ -738,7 +729,7 @@ pub fn split_args_for_threads(
     }
 }
 
-#[expect(clippy::must_use_candidate)]
+#[track_caller]
 pub fn delete_file_if_exists(path: &Path) -> bool {
     match fs::remove_file(path) {
         Ok(()) => true,
@@ -747,6 +738,7 @@ pub fn delete_file_if_exists(path: &Path) -> bool {
     }
 }
 
+#[track_caller]
 pub fn delete_dir_if_exists(path: &Path) {
     match fs::remove_dir_all(path) {
         Ok(()) => {},
diff --git a/src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs
index e3b125a8d5b..eb75d5576f5 100644
--- a/src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs
@@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::msrvs::Msrv;
 use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
 use clippy_utils::sugg::has_enclosing_paren;
-use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
+use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_from_proc_macro, is_lint_allowed, msrvs, std_or_core};
 use rustc_errors::Applicability;
 use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
 use rustc_lint::LateContext;
@@ -22,13 +22,12 @@ pub(super) fn check<'tcx>(
         && !matches!(target.ty.kind, TyKind::TraitObject(..))
         && let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind
         && !is_lint_allowed(cx, BORROW_AS_PTR, expr.hir_id)
+        // Fix #9884
+        && !is_expr_temporary_value(cx, e)
+        && !is_from_proc_macro(cx, expr)
     {
         let mut app = Applicability::MachineApplicable;
         let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0;
-        // Fix #9884
-        if is_expr_temporary_value(cx, e) {
-            return false;
-        }
 
         let (suggestion, span) = if msrv.meets(cx, msrvs::RAW_REF_OP) {
             // Make sure that the span to be replaced doesn't include parentheses, that could break the
diff --git a/src/tools/clippy/clippy_lints/src/casts/char_lit_as_u8.rs b/src/tools/clippy/clippy_lints/src/casts/char_lit_as_u8.rs
index a7d3868f76c..964eaf2a0a2 100644
--- a/src/tools/clippy/clippy_lints/src/casts/char_lit_as_u8.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/char_lit_as_u8.rs
@@ -4,18 +4,17 @@ use rustc_ast::LitKind;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind};
 use rustc_lint::LateContext;
-use rustc_middle::ty::{self, UintTy};
+use rustc_middle::ty::{self, Ty, UintTy};
 
 use super::CHAR_LIT_AS_U8;
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
-    if let ExprKind::Cast(e, _) = &expr.kind
-        && let ExprKind::Lit(l) = &e.kind
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from_expr: &Expr<'_>, cast_to: Ty<'_>) {
+    if let ExprKind::Lit(l) = &cast_from_expr.kind
         && let LitKind::Char(c) = l.node
-        && ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind()
+        && ty::Uint(UintTy::U8) == *cast_to.kind()
     {
         let mut applicability = Applicability::MachineApplicable;
-        let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
+        let snippet = snippet_with_applicability(cx, cast_from_expr.span, "'x'", &mut applicability);
 
         span_lint_and_then(
             cx,
diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs
index dcc439a272c..e25df9dd249 100644
--- a/src/tools/clippy/clippy_lints/src/casts/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs
@@ -871,6 +871,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
             if !expr.span.from_expansion() && unnecessary_cast::check(cx, expr, cast_from_expr, cast_from, cast_to) {
                 return;
             }
+            char_lit_as_u8::check(cx, expr, cast_from_expr, cast_to);
             cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv);
             ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
             as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
@@ -911,7 +912,6 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
             borrow_as_ptr::check_implicit_cast(cx, expr);
         }
         cast_ptr_alignment::check(cx, expr);
-        char_lit_as_u8::check(cx, expr);
         ptr_as_ptr::check(cx, expr, self.msrv);
         cast_slice_different_sizes::check(cx, expr, self.msrv);
         ptr_cast_constness::check_null_ptr_cast_method(cx, expr);
diff --git a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs
index ee0f3fa81c6..89075409098 100644
--- a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs
@@ -1,4 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_from_proc_macro;
 use clippy_utils::msrvs::{self, Msrv};
 use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::sugg::Sugg;
@@ -25,7 +26,7 @@ impl OmitFollowedCastReason<'_> {
     }
 }
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) {
     if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind
         && let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr))
         && let ty::RawPtr(_, from_mutbl) = cast_from.kind()
@@ -36,6 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
         // as explained here: https://github.com/rust-lang/rust/issues/60602.
         && to_pointee_ty.is_sized(cx.tcx, cx.typing_env())
         && msrv.meets(cx, msrvs::POINTER_CAST)
+        && !is_from_proc_macro(cx, expr)
     {
         let mut app = Applicability::MachineApplicable;
         let turbofish = match &cast_to_hir_ty.kind {
diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs
index 8f1c0296524..8f95c63a854 100644
--- a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs
+++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs
@@ -56,7 +56,7 @@ impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
 
 impl CognitiveComplexity {
     fn check<'tcx>(
-        &mut self,
+        &self,
         cx: &LateContext<'tcx>,
         kind: FnKind<'tcx>,
         decl: &'tcx FnDecl<'_>,
diff --git a/src/tools/clippy/clippy_lints/src/collapsible_if.rs b/src/tools/clippy/clippy_lints/src/collapsible_if.rs
index e3103e2d301..ad610fbd8d2 100644
--- a/src/tools/clippy/clippy_lints/src/collapsible_if.rs
+++ b/src/tools/clippy/clippy_lints/src/collapsible_if.rs
@@ -5,7 +5,7 @@ use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_w
 use clippy_utils::{span_contains_non_whitespace, tokenize_with_text};
 use rustc_ast::BinOpKind;
 use rustc_errors::Applicability;
-use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
+use rustc_hir::{Block, Expr, ExprKind, StmtKind};
 use rustc_lexer::TokenKind;
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::impl_lint_pass;
@@ -141,11 +141,7 @@ impl CollapsibleIf {
 
                     // Prevent "elseif"
                     // Check that the "else" is followed by whitespace
-                    let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
-                        !c.is_whitespace()
-                    } else {
-                        false
-                    };
+                    let requires_space = snippet(cx, up_to_else, "..").ends_with(|c: char| !c.is_whitespace());
                     let mut applicability = Applicability::MachineApplicable;
                     diag.span_suggestion(
                         else_block.span,
@@ -173,8 +169,7 @@ impl CollapsibleIf {
             && cx.tcx.hir_attrs(inner.hir_id).is_empty()
             && let ExprKind::If(check_inner, _, None) = &inner.kind
             && self.eligible_condition(cx, check_inner)
-            && let ctxt = expr.span.ctxt()
-            && inner.span.ctxt() == ctxt
+            && expr.span.eq_ctxt(inner.span)
             && !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code)
         {
             span_lint_and_then(
@@ -262,14 +257,9 @@ fn block_starts_with_significant_tokens(
 /// If `block` is a block with either one expression or a statement containing an expression,
 /// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
 fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
-    match block.stmts {
-        [] => block.expr,
-        [
-            Stmt {
-                kind: StmtKind::Semi(expr),
-                ..
-            },
-        ] if block.expr.is_none() => Some(expr),
+    match (block.stmts, block.expr) {
+        ([], expr) => expr,
+        ([stmt], None) if let StmtKind::Semi(expr) = stmt.kind => Some(expr),
         _ => None,
     }
 }
diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs
index e1cb08e361c..e67e8d9070f 100644
--- a/src/tools/clippy/clippy_lints/src/declared_lints.rs
+++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs
@@ -187,6 +187,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
     crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO,
     crate::from_str_radix_10::FROM_STR_RADIX_10_INFO,
     crate::functions::DOUBLE_MUST_USE_INFO,
+    crate::functions::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
     crate::functions::IMPL_TRAIT_IN_PARAMS_INFO,
     crate::functions::MISNAMED_GETTERS_INFO,
     crate::functions::MUST_USE_CANDIDATE_INFO,
@@ -505,7 +506,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
     crate::misc::USED_UNDERSCORE_BINDING_INFO,
     crate::misc::USED_UNDERSCORE_ITEMS_INFO,
     crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
-    crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
     crate::misc_early::MIXED_CASE_HEX_LITERALS_INFO,
     crate::misc_early::REDUNDANT_AT_REST_PATTERN_INFO,
     crate::misc_early::REDUNDANT_PATTERN_INFO,
diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs
index 995a1209595..9aa2f3cf0a5 100644
--- a/src/tools/clippy/clippy_lints/src/dereference.rs
+++ b/src/tools/clippy/clippy_lints/src/dereference.rs
@@ -1,12 +1,11 @@
 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
 use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
 use clippy_utils::sugg::has_enclosing_paren;
-use clippy_utils::ty::{implements_trait, is_manually_drop};
+use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop};
 use clippy_utils::{
     DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local,
     peel_middle_ty_refs,
 };
-use core::mem;
 use rustc_ast::util::parser::ExprPrecedence;
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::Applicability;
@@ -707,14 +706,6 @@ fn try_parse_ref_op<'tcx>(
     ))
 }
 
-// Checks if the adjustments contains a deref of `ManuallyDrop<_>`
-fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool {
-    adjustments.iter().any(|a| {
-        let ty = mem::replace(&mut ty, a.target);
-        matches!(a.kind, Adjust::Deref(Some(ref op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty)
-    })
-}
-
 // Checks whether the type for a deref call actually changed the type, not just the mutability of
 // the reference.
 fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs
index d27d68d3866..eca3bc390d7 100644
--- a/src/tools/clippy/clippy_lints/src/doc/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs
@@ -1139,12 +1139,12 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
                         None,
                         "a backtick may be missing a pair",
                     );
+                    text_to_check.clear();
                 } else {
-                    for (text, range, assoc_code_level) in text_to_check {
+                    for (text, range, assoc_code_level) in text_to_check.drain(..) {
                         markdown::check(cx, valid_idents, &text, &fragments, range, assoc_code_level, blockquote_level);
                     }
                 }
-                text_to_check = Vec::new();
             },
             Start(FootnoteDefinition(..)) => in_footnote_definition = true,
             End(TagEnd::FootnoteDefinition) => in_footnote_definition = false,
diff --git a/src/tools/clippy/clippy_lints/src/duplicate_mod.rs b/src/tools/clippy/clippy_lints/src/duplicate_mod.rs
index ce551a64d99..759b7b6837b 100644
--- a/src/tools/clippy/clippy_lints/src/duplicate_mod.rs
+++ b/src/tools/clippy/clippy_lints/src/duplicate_mod.rs
@@ -63,7 +63,7 @@ impl_lint_pass!(DuplicateMod => [DUPLICATE_MOD]);
 
 impl EarlyLintPass for DuplicateMod {
     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
-        if let ItemKind::Mod(_, _, ModKind::Loaded(_, Inline::No, mod_spans, _)) = &item.kind
+        if let ItemKind::Mod(_, _, ModKind::Loaded(_, Inline::No { .. }, mod_spans)) = &item.kind
             && let FileName::Real(real) = cx.sess().source_map().span_to_filename(mod_spans.inner_span)
             && let Some(local_path) = real.into_local_path()
             && let Ok(absolute_path) = local_path.canonicalize()
diff --git a/src/tools/clippy/clippy_lints/src/empty_line_after.rs b/src/tools/clippy/clippy_lints/src/empty_line_after.rs
index 3bd74856165..76e67b1154b 100644
--- a/src/tools/clippy/clippy_lints/src/empty_line_after.rs
+++ b/src/tools/clippy/clippy_lints/src/empty_line_after.rs
@@ -442,7 +442,7 @@ impl EmptyLineAfter {
                 None => span.shrink_to_lo(),
             },
             mod_items: match kind {
-                ItemKind::Mod(_, _, ModKind::Loaded(items, _, _, _)) => items
+                ItemKind::Mod(_, _, ModKind::Loaded(items, _, _)) => items
                     .iter()
                     .filter(|i| !matches!(i.span.ctxt().outer_expn_data().kind, ExpnKind::AstPass(_)))
                     .map(|i| i.id)
diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs
index e467246741c..0eefc2f6109 100644
--- a/src/tools/clippy/clippy_lints/src/eta_reduction.rs
+++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs
@@ -231,9 +231,13 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
                                     _ => (),
                                 }
                             }
+                            let replace_with = match callee_ty_adjusted.kind() {
+                                ty::FnDef(def, _) => cx.tcx.def_descr(*def),
+                                _ => "function",
+                            };
                             diag.span_suggestion(
                                 expr.span,
-                                "replace the closure with the function itself",
+                                format!("replace the closure with the {replace_with} itself"),
                                 snippet,
                                 Applicability::MachineApplicable,
                             );
diff --git a/src/tools/clippy/clippy_lints/src/excessive_nesting.rs b/src/tools/clippy/clippy_lints/src/excessive_nesting.rs
index 1d3ae894944..5368701c304 100644
--- a/src/tools/clippy/clippy_lints/src/excessive_nesting.rs
+++ b/src/tools/clippy/clippy_lints/src/excessive_nesting.rs
@@ -164,7 +164,7 @@ impl Visitor<'_> for NestingVisitor<'_, '_> {
         }
 
         match &item.kind {
-            ItemKind::Trait(_) | ItemKind::Impl(_) | ItemKind::Mod(.., ModKind::Loaded(_, Inline::Yes, _, _)) => {
+            ItemKind::Trait(_) | ItemKind::Impl(_) | ItemKind::Mod(.., ModKind::Loaded(_, Inline::Yes, _)) => {
                 self.nest_level += 1;
 
                 if !self.check_indent(item.span, item.id) {
diff --git a/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs b/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs
index b816963cc82..d5873b3f85a 100644
--- a/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs
+++ b/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::sugg::Sugg;
-use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
+use clippy_utils::ty::is_type_lang_item;
 use clippy_utils::{is_in_const_context, is_integer_literal, sym};
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, LangItem, PrimTy, QPath, TyKind, def};
@@ -89,5 +89,5 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
 
 /// Checks if a Ty is `String` or `&str`
 fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
-    is_type_lang_item(cx, ty, LangItem::String) || is_type_diagnostic_item(cx, ty, sym::str)
+    is_type_lang_item(cx, ty, LangItem::String) || ty.peel_refs().is_str()
 }
diff --git a/src/tools/clippy/clippy_lints/src/functions/duplicate_underscore_argument.rs b/src/tools/clippy/clippy_lints/src/functions/duplicate_underscore_argument.rs
new file mode 100644
index 00000000000..b15d1b1bb79
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/functions/duplicate_underscore_argument.rs
@@ -0,0 +1,34 @@
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::PatKind;
+use rustc_ast::visit::FnKind;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_lint::EarlyContext;
+use rustc_span::Span;
+
+use super::DUPLICATE_UNDERSCORE_ARGUMENT;
+
+pub(super) fn check(cx: &EarlyContext<'_>, fn_kind: FnKind<'_>) {
+    let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
+
+    for arg in &fn_kind.decl().inputs {
+        if let PatKind::Ident(_, ident, None) = arg.pat.kind {
+            let arg_name = ident.to_string();
+
+            if let Some(arg_name) = arg_name.strip_prefix('_') {
+                if let Some(correspondence) = registered_names.get(arg_name) {
+                    span_lint(
+                        cx,
+                        DUPLICATE_UNDERSCORE_ARGUMENT,
+                        *correspondence,
+                        format!(
+                            "`{arg_name}` already exists, having another argument having almost the same \
+                                 name makes code comprehension and documentation more difficult"
+                        ),
+                    );
+                }
+            } else {
+                registered_names.insert(arg_name, arg.pat.span);
+            }
+        }
+    }
+}
diff --git a/src/tools/clippy/clippy_lints/src/functions/mod.rs b/src/tools/clippy/clippy_lints/src/functions/mod.rs
index 6051dc9479b..ca5ea901814 100644
--- a/src/tools/clippy/clippy_lints/src/functions/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/mod.rs
@@ -1,3 +1,4 @@
+mod duplicate_underscore_argument;
 mod impl_trait_in_params;
 mod misnamed_getters;
 mod must_use;
@@ -11,16 +12,40 @@ mod too_many_lines;
 use clippy_config::Conf;
 use clippy_utils::msrvs::Msrv;
 use clippy_utils::paths::{PathNS, lookup_path_str};
+use rustc_ast::{self as ast, visit};
 use rustc_hir as hir;
 use rustc_hir::intravisit;
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
 use rustc_middle::ty::TyCtxt;
-use rustc_session::impl_lint_pass;
+use rustc_session::{declare_lint_pass, impl_lint_pass};
 use rustc_span::Span;
 use rustc_span::def_id::{DefIdSet, LocalDefId};
 
 declare_clippy_lint! {
     /// ### What it does
+    /// Checks for function arguments having the similar names
+    /// differing by an underscore.
+    ///
+    /// ### Why is this bad?
+    /// It affects code readability.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// fn foo(a: i32, _a: i32) {}
+    /// ```
+    ///
+    /// Use instead:
+    /// ```no_run
+    /// fn bar(a: i32, _b: i32) {}
+    /// ```
+    #[clippy::version = "pre 1.29.0"]
+    pub DUPLICATE_UNDERSCORE_ARGUMENT,
+    style,
+    "function arguments having names which only differ by an underscore"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
     /// Checks for functions with too many parameters.
     ///
     /// ### Why is this bad?
@@ -448,6 +473,14 @@ declare_clippy_lint! {
     "function signature uses `&Option<T>` instead of `Option<&T>`"
 }
 
+declare_lint_pass!(EarlyFunctions => [DUPLICATE_UNDERSCORE_ARGUMENT]);
+
+impl EarlyLintPass for EarlyFunctions {
+    fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: visit::FnKind<'_>, _: Span, _: ast::NodeId) {
+        duplicate_underscore_argument::check(cx, fn_kind);
+    }
+}
+
 pub struct Functions {
     too_many_arguments_threshold: u64,
     too_many_lines_threshold: u64,
@@ -503,7 +536,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
     ) {
         let hir_id = cx.tcx.local_def_id_to_hir_id(def_id);
         too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
-        too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold);
+        too_many_lines::check_fn(cx, kind, body, span, def_id, self.too_many_lines_threshold);
         not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id);
         misnamed_getters::check_fn(cx, kind, decl, body, span);
         impl_trait_in_params::check_fn(cx, &kind, body, hir_id);
diff --git a/src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs b/src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs
index 0a7c6e9d5f8..f8e8f5544b9 100644
--- a/src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs
@@ -6,6 +6,7 @@ use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind, Node, TraitRef};
 use rustc_lint::LateContext;
 use rustc_span::Span;
 use rustc_span::symbol::{Ident, kw};
+use std::iter;
 
 use super::RENAMED_FUNCTION_PARAMS;
 
@@ -58,16 +59,11 @@ impl RenamedFnArgs {
         let mut renamed: Vec<(Span, String)> = vec![];
 
         debug_assert!(default_idents.size_hint() == current_idents.size_hint());
-        while let (Some(default_ident), Some(current_ident)) = (default_idents.next(), current_idents.next()) {
+        for (default_ident, current_ident) in iter::zip(default_idents, current_idents) {
             let has_name_to_check = |ident: Option<Ident>| {
-                if let Some(ident) = ident
-                    && ident.name != kw::Underscore
-                    && !ident.name.as_str().starts_with('_')
-                {
-                    Some(ident)
-                } else {
-                    None
-                }
+                ident
+                    .filter(|ident| ident.name != kw::Underscore)
+                    .filter(|ident| !ident.name.as_str().starts_with('_'))
             };
 
             if let Some(default_ident) = has_name_to_check(default_ident)
@@ -97,8 +93,7 @@ fn trait_item_def_id_of_impl(cx: &LateContext<'_>, target: OwnerId) -> Option<De
 }
 
 fn is_from_ignored_trait(of_trait: &TraitRef<'_>, ignored_traits: &DefIdSet) -> bool {
-    let Some(trait_did) = of_trait.trait_def_id() else {
-        return false;
-    };
-    ignored_traits.contains(&trait_did)
+    of_trait
+        .trait_def_id()
+        .is_some_and(|trait_did| ignored_traits.contains(&trait_did))
 }
diff --git a/src/tools/clippy/clippy_lints/src/functions/result.rs b/src/tools/clippy/clippy_lints/src/functions/result.rs
index bb98ae82611..1f2fce687ed 100644
--- a/src/tools/clippy/clippy_lints/src/functions/result.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/result.rs
@@ -97,11 +97,7 @@ fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: S
 
 fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) {
     if let ty::Adt(adt, subst) = err_ty.kind()
-        && let Some(local_def_id) = err_ty
-            .ty_adt_def()
-            .expect("already checked this is adt")
-            .did()
-            .as_local()
+        && let Some(local_def_id) = adt.did().as_local()
         && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_def_id)
         && let hir::ItemKind::Enum(_, _, ref def) = item.kind
     {
diff --git a/src/tools/clippy/clippy_lints/src/functions/too_many_lines.rs b/src/tools/clippy/clippy_lints/src/functions/too_many_lines.rs
index 4f90d9655b4..33eede8e65a 100644
--- a/src/tools/clippy/clippy_lints/src/functions/too_many_lines.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/too_many_lines.rs
@@ -1,6 +1,7 @@
 use clippy_utils::diagnostics::span_lint;
 use clippy_utils::source::SpanRangeExt;
 use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
 use rustc_hir::intravisit::FnKind;
 use rustc_lint::{LateContext, LintContext};
 use rustc_span::Span;
@@ -10,8 +11,9 @@ use super::TOO_MANY_LINES;
 pub(super) fn check_fn(
     cx: &LateContext<'_>,
     kind: FnKind<'_>,
-    span: Span,
     body: &hir::Body<'_>,
+    span: Span,
+    def_id: LocalDefId,
     too_many_lines_threshold: u64,
 ) {
     // Closures must be contained in a parent body, which will be checked for `too_many_lines`.
@@ -74,7 +76,7 @@ pub(super) fn check_fn(
         span_lint(
             cx,
             TOO_MANY_LINES,
-            span,
+            cx.tcx.def_span(def_id),
             format!("this function has too many lines ({line_count}/{too_many_lines_threshold})"),
         );
     }
diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs
index 6beddc1be14..57deb011f2b 100644
--- a/src/tools/clippy/clippy_lints/src/len_zero.rs
+++ b/src/tools/clippy/clippy_lints/src/len_zero.rs
@@ -176,12 +176,11 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
         if let ExprKind::Let(lt) = expr.kind
             && match lt.pat.kind {
                 PatKind::Slice([], None, []) => true,
-                PatKind::Expr(lit) => match lit.kind {
-                    PatExprKind::Lit { lit, .. } => match lit.node {
-                        LitKind::Str(lit, _) => lit.as_str().is_empty(),
-                        _ => false,
-                    },
-                    _ => false,
+                PatKind::Expr(lit)
+                    if let PatExprKind::Lit { lit, .. } = lit.kind
+                        && let LitKind::Str(lit, _) = lit.node =>
+                {
+                    lit.as_str().is_empty()
                 },
                 _ => false,
             }
@@ -336,33 +335,23 @@ fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&
 }
 
 fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool {
-    if let Some(generic_args) = segment.args {
-        if generic_args.args.is_empty() {
-            return false;
-        }
-        let arg = &generic_args.args[0];
-        if let GenericArg::Type(rustc_hir::Ty {
-            kind: TyKind::Path(QPath::Resolved(_, path)),
-            ..
-        }) = arg
-        {
-            let segments = &path.segments;
-            let segment = &segments[0];
-            let res = &segment.res;
-            if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
-                return true;
-            }
-        }
+    if let Some(generic_args) = segment.args
+        && let [GenericArg::Type(ty), ..] = &generic_args.args
+        && let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
+        && let [segment, ..] = &path.segments
+        && matches!(segment.res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_)))
+    {
+        true
+    } else {
+        false
     }
-
-    false
 }
 
 fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<LenOutput> {
     if let Some(segment) = extract_future_output(cx, sig.output()) {
         let res = segment.res;
 
-        if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
+        if matches!(res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_))) {
             return Some(LenOutput::Integral);
         }
 
diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs
index 844bc1b0e39..d468993e744 100644
--- a/src/tools/clippy/clippy_lints/src/lib.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.rs
@@ -556,6 +556,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
     store.register_late_pass(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks));
     store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default());
     store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(conf)));
+    store.register_early_pass(|| Box::new(functions::EarlyFunctions));
     store.register_late_pass(move |tcx| Box::new(functions::Functions::new(tcx, conf)));
     store.register_late_pass(move |_| Box::new(doc::Documentation::new(conf)));
     store.register_early_pass(move || Box::new(doc::Documentation::new(conf)));
@@ -600,7 +601,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
     store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf)));
     store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
     store.register_late_pass(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf)));
-    store.register_early_pass(|| Box::new(reference::DerefAddrOf));
+    store.register_late_pass(|_| Box::new(reference::DerefAddrOf));
     store.register_early_pass(|| Box::new(double_parens::DoubleParens));
     let format_args = format_args_storage.clone();
     store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone())));
diff --git a/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs b/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs
index 797ff1f3986..a71e6963f8c 100644
--- a/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs
@@ -1,10 +1,11 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
 use hir::intravisit::{Visitor, walk_expr};
-use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind};
 use rustc_ast::Label;
 use rustc_errors::Applicability;
-use rustc_hir as hir;
+use rustc_hir::{
+    self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind,
+};
 use rustc_lint::{LateContext, LintContext};
 use rustc_span::sym;
 
@@ -29,6 +30,10 @@ pub(super) fn check<'tcx>(
         return;
     }
 
+    if is_inside_unawaited_async_block(cx, expr) {
+        return;
+    }
+
     if expr.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, expr) {
         return;
     }
@@ -60,6 +65,39 @@ pub(super) fn check<'tcx>(
     }
 }
 
+/// Check if the given expression is inside an async block that is not being awaited.
+/// This helps avoid false positives when async blocks are spawned or assigned to variables.
+fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    let current_hir_id = expr.hir_id;
+    for (_, parent_node) in cx.tcx.hir_parent_iter(current_hir_id) {
+        if let Node::Expr(Expr {
+            kind:
+                ExprKind::Closure(Closure {
+                    kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
+                    ..
+                }),
+            ..
+        }) = parent_node
+        {
+            return !is_async_block_awaited(cx, expr);
+        }
+    }
+    false
+}
+
+fn is_async_block_awaited(cx: &LateContext<'_>, async_expr: &Expr<'_>) -> bool {
+    for (_, parent_node) in cx.tcx.hir_parent_iter(async_expr.hir_id) {
+        if let Node::Expr(Expr {
+            kind: ExprKind::Match(_, _, hir::MatchSource::AwaitDesugar),
+            ..
+        }) = parent_node
+        {
+            return true;
+        }
+    }
+    false
+}
+
 fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<FnRetTy<'tcx>> {
     for (_, parent_node) in cx.tcx.hir_parent_iter(expr.hir_id) {
         match parent_node {
@@ -67,8 +105,8 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
             // This is because we still need to backtrack one parent node to get the `OpaqueDef` ty.
             Node::Expr(Expr {
                 kind:
-                    ExprKind::Closure(hir::Closure {
-                        kind: hir::ClosureKind::Coroutine(_),
+                    ExprKind::Closure(Closure {
+                        kind: ClosureKind::Coroutine(_),
                         ..
                     }),
                 ..
@@ -90,7 +128,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
                 ..
             })
             | Node::Expr(Expr {
-                kind: ExprKind::Closure(hir::Closure { fn_decl: decl, .. }),
+                kind: ExprKind::Closure(Closure { fn_decl: decl, .. }),
                 ..
             }) => return Some(decl.output),
             _ => (),
diff --git a/src/tools/clippy/clippy_lints/src/manual_let_else.rs b/src/tools/clippy/clippy_lints/src/manual_let_else.rs
index 1f9a943f13d..5a7967bbf94 100644
--- a/src/tools/clippy/clippy_lints/src/manual_let_else.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_let_else.rs
@@ -49,7 +49,7 @@ declare_clippy_lint! {
 }
 
 impl<'tcx> QuestionMark {
-    pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
+    pub(crate) fn check_manual_let_else(&self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
         if let StmtKind::Let(local) = stmt.kind
             && let Some(init) = local.init
             && local.els.is_none()
diff --git a/src/tools/clippy/clippy_lints/src/matches/match_bool.rs b/src/tools/clippy/clippy_lints/src/matches/match_bool.rs
index b90cf6357c5..a2c8741f4f7 100644
--- a/src/tools/clippy/clippy_lints/src/matches/match_bool.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/match_bool.rs
@@ -12,7 +12,11 @@ use super::MATCH_BOOL;
 
 pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
     // Type of expression is `bool`.
-    if *cx.typeck_results().expr_ty(scrutinee).kind() == ty::Bool {
+    if *cx.typeck_results().expr_ty(scrutinee).kind() == ty::Bool
+        && arms
+            .iter()
+            .all(|arm| arm.pat.walk_short(|p| !matches!(p.kind, PatKind::Binding(..))))
+    {
         span_lint_and_then(
             cx,
             MATCH_BOOL,
diff --git a/src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs b/src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs
index 5445ee1f042..5934ec40993 100644
--- a/src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs
@@ -17,6 +17,11 @@ where
         return;
     }
 
+    // `!` cannot be deref-ed
+    if cx.typeck_results().expr_ty(scrutinee).is_never() {
+        return;
+    }
+
     let (first_sugg, msg, title);
     let ctxt = expr.span.ctxt();
     let mut app = Applicability::Unspecified;
diff --git a/src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs b/src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs
index 8b4c1700051..eb8b16e1561 100644
--- a/src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs
@@ -54,7 +54,7 @@ impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> {
 }
 
 impl MatchExprVisitor<'_, '_> {
-    fn case_altered(&mut self, segment_ident: Symbol, receiver: &Expr<'_>) -> ControlFlow<CaseMethod> {
+    fn case_altered(&self, segment_ident: Symbol, receiver: &Expr<'_>) -> ControlFlow<CaseMethod> {
         if let Some(case_method) = get_case_method(segment_ident) {
             let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs();
 
diff --git a/src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs b/src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs
index 6d841853fbe..578865c3291 100644
--- a/src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait};
-use clippy_utils::{is_mutable, is_trait_method, path_to_local, sym};
+use clippy_utils::{is_mutable, is_trait_method, path_to_local_with_projections, sym};
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, Node, PatKind};
 use rustc_lint::LateContext;
@@ -37,7 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
         // TODO: Change this to lint only when the referred iterator is not used later. If it is used later,
         // changing to `next_back()` may change its behavior.
         if !(is_mutable(cx, self_expr) || self_type.is_ref()) {
-            if let Some(hir_id) = path_to_local(self_expr)
+            if let Some(hir_id) = path_to_local_with_projections(self_expr)
                 && let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
                 && let PatKind::Binding(_, _, ident, _) = pat.kind
             {
diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
index 6e5da5bda8c..818e26f8aa1 100644
--- a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
@@ -15,7 +15,6 @@ use std::ops::ControlFlow;
 use super::EXPECT_FUN_CALL;
 
 /// Checks for the `EXPECT_FUN_CALL` lint.
-#[allow(clippy::too_many_lines)]
 pub(super) fn check<'tcx>(
     cx: &LateContext<'tcx>,
     format_args_storage: &FormatArgsStorage,
@@ -25,43 +24,6 @@ pub(super) fn check<'tcx>(
     receiver: &'tcx hir::Expr<'tcx>,
     args: &'tcx [hir::Expr<'tcx>],
 ) {
-    // Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
-    // `&str`
-    fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
-        let mut arg_root = peel_blocks(arg);
-        loop {
-            arg_root = match &arg_root.kind {
-                hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
-                hir::ExprKind::MethodCall(method_name, receiver, [], ..) => {
-                    if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && {
-                        let arg_type = cx.typeck_results().expr_ty(receiver);
-                        let base_type = arg_type.peel_refs();
-                        base_type.is_str() || is_type_lang_item(cx, base_type, hir::LangItem::String)
-                    } {
-                        receiver
-                    } else {
-                        break;
-                    }
-                },
-                _ => break,
-            };
-        }
-        arg_root
-    }
-
-    fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool {
-        for_each_expr(cx, arg, |expr| {
-            if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. })
-                && !is_inside_always_const_context(cx.tcx, expr.hir_id)
-            {
-                ControlFlow::Break(())
-            } else {
-                ControlFlow::Continue(())
-            }
-        })
-        .is_some()
-    }
-
     if name == sym::expect
         && let [arg] = args
         && let arg_root = get_arg_root(cx, arg)
@@ -114,3 +76,40 @@ pub(super) fn check<'tcx>(
         );
     }
 }
+
+/// Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
+/// `&str`
+fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
+    let mut arg_root = peel_blocks(arg);
+    loop {
+        arg_root = match &arg_root.kind {
+            hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
+            hir::ExprKind::MethodCall(method_name, receiver, [], ..) => {
+                if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && {
+                    let arg_type = cx.typeck_results().expr_ty(receiver);
+                    let base_type = arg_type.peel_refs();
+                    base_type.is_str() || is_type_lang_item(cx, base_type, hir::LangItem::String)
+                } {
+                    receiver
+                } else {
+                    break;
+                }
+            },
+            _ => break,
+        };
+    }
+    arg_root
+}
+
+fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool {
+    for_each_expr(cx, arg, |expr| {
+        if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. })
+            && !is_inside_always_const_context(cx.tcx, expr.hir_id)
+        {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
+        }
+    })
+    .is_some()
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs
index 4dd54cf1974..5b8457bdd16 100644
--- a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs
@@ -106,7 +106,7 @@ enum CheckResult<'tcx> {
 
 impl<'tcx> OffendingFilterExpr<'tcx> {
     pub fn check_map_call(
-        &mut self,
+        &self,
         cx: &LateContext<'tcx>,
         map_body: &'tcx Body<'tcx>,
         map_param_id: HirId,
@@ -413,7 +413,7 @@ fn is_find_or_filter<'a>(
         }
 
         && let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind
-        && let Some(mut offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id)
+        && let Some(offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id)
 
         && let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind
         && let map_body = cx.tcx.hir_body(map_body_id)
diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_next.rs b/src/tools/clippy/clippy_lints/src/methods/filter_next.rs
index 6c1a14fc882..72f83b245a0 100644
--- a/src/tools/clippy/clippy_lints/src/methods/filter_next.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/filter_next.rs
@@ -1,4 +1,5 @@
 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::path_to_local_with_projections;
 use clippy_utils::source::snippet;
 use clippy_utils::ty::implements_trait;
 use rustc_ast::{BindingMode, Mutability};
@@ -9,21 +10,6 @@ use rustc_span::sym;
 
 use super::FILTER_NEXT;
 
-fn path_to_local(expr: &hir::Expr<'_>) -> Option<hir::HirId> {
-    match expr.kind {
-        hir::ExprKind::Field(f, _) => path_to_local(f),
-        hir::ExprKind::Index(recv, _, _) => path_to_local(recv),
-        hir::ExprKind::Path(hir::QPath::Resolved(
-            _,
-            hir::Path {
-                res: rustc_hir::def::Res::Local(local),
-                ..
-            },
-        )) => Some(*local),
-        _ => None,
-    }
-}
-
 /// lint use of `filter().next()` for `Iterators`
 pub(super) fn check<'tcx>(
     cx: &LateContext<'tcx>,
@@ -44,7 +30,7 @@ pub(super) fn check<'tcx>(
             let iter_snippet = snippet(cx, recv.span, "..");
             // add note if not multi-line
             span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| {
-                let (applicability, pat) = if let Some(id) = path_to_local(recv)
+                let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv)
                     && let hir::Node::Pat(pat) = cx.tcx.hir_node(id)
                     && let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind
                 {
diff --git a/src/tools/clippy/clippy_lints/src/misc_early/mod.rs b/src/tools/clippy/clippy_lints/src/misc_early/mod.rs
index f880f1f329f..f988323a8c1 100644
--- a/src/tools/clippy/clippy_lints/src/misc_early/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/misc_early/mod.rs
@@ -7,12 +7,9 @@ mod unneeded_field_pattern;
 mod unneeded_wildcard_pattern;
 mod zero_prefixed_literal;
 
-use clippy_utils::diagnostics::span_lint;
 use clippy_utils::source::snippet_opt;
-use rustc_ast::ast::{Expr, ExprKind, Generics, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
+use rustc_ast::ast::{Expr, ExprKind, Generics, LitFloatType, LitIntType, LitKind, Pat};
 use rustc_ast::token;
-use rustc_ast::visit::FnKind;
-use rustc_data_structures::fx::FxHashMap;
 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_session::declare_lint_pass;
 use rustc_span::Span;
@@ -62,29 +59,6 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
-    /// Checks for function arguments having the similar names
-    /// differing by an underscore.
-    ///
-    /// ### Why is this bad?
-    /// It affects code readability.
-    ///
-    /// ### Example
-    /// ```no_run
-    /// fn foo(a: i32, _a: i32) {}
-    /// ```
-    ///
-    /// Use instead:
-    /// ```no_run
-    /// fn bar(a: i32, _b: i32) {}
-    /// ```
-    #[clippy::version = "pre 1.29.0"]
-    pub DUPLICATE_UNDERSCORE_ARGUMENT,
-    style,
-    "function arguments having names which only differ by an underscore"
-}
-
-declare_clippy_lint! {
-    /// ### What it does
     /// Warns on hexadecimal literals with mixed-case letter
     /// digits.
     ///
@@ -330,7 +304,6 @@ declare_clippy_lint! {
 
 declare_lint_pass!(MiscEarlyLints => [
     UNNEEDED_FIELD_PATTERN,
-    DUPLICATE_UNDERSCORE_ARGUMENT,
     MIXED_CASE_HEX_LITERALS,
     UNSEPARATED_LITERAL_SUFFIX,
     SEPARATED_LITERAL_SUFFIX,
@@ -359,32 +332,6 @@ impl EarlyLintPass for MiscEarlyLints {
         unneeded_wildcard_pattern::check(cx, pat);
     }
 
-    fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
-        let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
-
-        for arg in &fn_kind.decl().inputs {
-            if let PatKind::Ident(_, ident, None) = arg.pat.kind {
-                let arg_name = ident.to_string();
-
-                if let Some(arg_name) = arg_name.strip_prefix('_') {
-                    if let Some(correspondence) = registered_names.get(arg_name) {
-                        span_lint(
-                            cx,
-                            DUPLICATE_UNDERSCORE_ARGUMENT,
-                            *correspondence,
-                            format!(
-                                "`{arg_name}` already exists, having another argument having almost the same \
-                                 name makes code comprehension and documentation more difficult"
-                            ),
-                        );
-                    }
-                } else {
-                    registered_names.insert(arg_name, arg.pat.span);
-                }
-            }
-        }
-    }
-
     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
         if expr.span.in_external_macro(cx.sess().source_map()) {
             return;
@@ -404,7 +351,7 @@ impl MiscEarlyLints {
         // See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
         // FIXME: Find a better way to detect those cases.
         let lit_snip = match snippet_opt(cx, span) {
-            Some(snip) if snip.chars().next().is_some_and(|c| c.is_ascii_digit()) => snip,
+            Some(snip) if snip.starts_with(|c: char| c.is_ascii_digit()) => snip,
             _ => return,
         };
 
diff --git a/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs b/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs
index a489c0a4a5a..3b44d4b60d3 100644
--- a/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs
+++ b/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs
@@ -134,7 +134,7 @@ impl<'tcx> DivergenceVisitor<'_, 'tcx> {
         }
     }
 
-    fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) {
+    fn report_diverging_sub_expr(&self, e: &Expr<'_>) {
         if let Some(macro_call) = root_macro_call_first_node(self.cx, e)
             && self.cx.tcx.is_diagnostic_item(sym::todo_macro, macro_call.def_id)
         {
diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs
index fa5afcc0087..6ae26156bc4 100644
--- a/src/tools/clippy/clippy_lints/src/needless_bool.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs
@@ -166,7 +166,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
                     applicability,
                 );
             };
-            if let Some((a, b)) = fetch_bool_block(then).and_then(|a| Some((a, fetch_bool_block(else_expr)?))) {
+            if let Some(a) = fetch_bool_block(then)
+                && let Some(b) = fetch_bool_block(else_expr)
+            {
                 match (a, b) {
                     (RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => {
                         span_lint(
diff --git a/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs b/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
index 120a4b98a65..c7c4976aeb7 100644
--- a/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
@@ -59,7 +59,7 @@ declare_clippy_lint! {
 
 pub struct NeedlessBorrowsForGenericArgs<'tcx> {
     /// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
-    /// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
+    /// [`needless_borrow_count`] to determine when a borrowed expression can instead
     /// be moved.
     possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
 
diff --git a/src/tools/clippy/clippy_lints/src/no_effect.rs b/src/tools/clippy/clippy_lints/src/no_effect.rs
index 72e6503e7e4..0d6666eed45 100644
--- a/src/tools/clippy/clippy_lints/src/no_effect.rs
+++ b/src/tools/clippy/clippy_lints/src/no_effect.rs
@@ -305,11 +305,12 @@ fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
             for e in reduced {
                 if let Some(snip) = e.span.get_source_text(cx) {
                     snippet.push_str(&snip);
-                    snippet.push(';');
+                    snippet.push_str("; ");
                 } else {
                     return;
                 }
             }
+            snippet.pop(); // remove the last space
             span_lint_hir_and_then(
                 cx,
                 UNNECESSARY_OPERATION,
diff --git a/src/tools/clippy/clippy_lints/src/non_copy_const.rs b/src/tools/clippy/clippy_lints/src/non_copy_const.rs
index 8a5a6f4a4dc..2fffc4244a7 100644
--- a/src/tools/clippy/clippy_lints/src/non_copy_const.rs
+++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs
@@ -92,7 +92,7 @@ declare_clippy_lint! {
     /// ```
     #[clippy::version = "pre 1.29.0"]
     pub DECLARE_INTERIOR_MUTABLE_CONST,
-    style,
+    suspicious,
     "declaring `const` with interior mutability"
 }
 
diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
index c5873589b26..1961ac1516d 100644
--- a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
+++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
@@ -248,6 +248,11 @@ impl SimilarNamesNameVisitor<'_, '_, '_> {
                 continue;
             }
 
+            // Skip similarity check if both names are exactly 3 characters
+            if count == 3 && existing_name.len == 3 {
+                continue;
+            }
+
             let dissimilar = match existing_name.len.cmp(&count) {
                 Ordering::Greater => existing_name.len - count != 1 || levenstein_not_1(interned_name, existing_str),
                 Ordering::Less => count - existing_name.len != 1 || levenstein_not_1(existing_str, interned_name),
diff --git a/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs b/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs
index ba8f6354d97..a42763172f5 100644
--- a/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs
+++ b/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs
@@ -173,7 +173,7 @@ impl Params {
     }
 
     /// Sets the `apply_lint` flag on each parameter.
-    fn flag_for_linting(&mut self) {
+    fn flag_for_linting(&self) {
         // Stores the list of parameters currently being resolved. Needed to avoid cycles.
         let mut eval_stack = Vec::new();
         for param in &self.params {
diff --git a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
index 466beb04b07..ea5b81aec31 100644
--- a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
@@ -325,7 +325,7 @@ impl ArithmeticSideEffects {
         self.issue_lint(cx, expr);
     }
 
-    fn should_skip_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool {
+    fn should_skip_expr<'tcx>(&self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool {
         is_lint_allowed(cx, ARITHMETIC_SIDE_EFFECTS, expr.hir_id)
             || self.expr_span.is_some()
             || self.const_span.is_some_and(|sp| sp.contains(expr.span))
diff --git a/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs b/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs
index e6be536ca0f..9b1b063c473 100644
--- a/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs
@@ -13,7 +13,7 @@ pub struct Context {
     const_span: Option<Span>,
 }
 impl Context {
-    fn skip_expr(&mut self, e: &hir::Expr<'_>) -> bool {
+    fn skip_expr(&self, e: &hir::Expr<'_>) -> bool {
         self.expr_id.is_some() || self.const_span.is_some_and(|span| span.contains(e.span))
     }
 
diff --git a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs
index d7b4a03aa53..1b1e77bbea8 100644
--- a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs
+++ b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs
@@ -120,7 +120,7 @@ impl PassByRefOrValue {
         }
     }
 
-    fn check_poly_fn(&mut self, cx: &LateContext<'_>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option<Span>) {
+    fn check_poly_fn(&self, cx: &LateContext<'_>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option<Span>) {
         if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
             return;
         }
diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs
index b3058c51afd..9eed46460a6 100644
--- a/src/tools/clippy/clippy_lints/src/ptr.rs
+++ b/src/tools/clippy/clippy_lints/src/ptr.rs
@@ -237,7 +237,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
             .collect();
         let results = check_ptr_arg_usage(cx, body, &lint_args);
 
-        for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
+        for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) {
             span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| {
                 diag.multipart_suggestion(
                     "change this to",
@@ -386,7 +386,6 @@ impl<'tcx> DerefTy<'tcx> {
     }
 }
 
-#[expect(clippy::too_many_lines)]
 fn check_fn_args<'cx, 'tcx: 'cx>(
     cx: &'cx LateContext<'tcx>,
     fn_sig: ty::FnSig<'tcx>,
@@ -413,13 +412,13 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
                     Some(sym::Vec) => (
                         [(sym::clone, ".to_owned()")].as_slice(),
                         DerefTy::Slice(
-                            name.args.and_then(|args| args.args.first()).and_then(|arg| {
-                                if let GenericArg::Type(ty) = arg {
-                                    Some(ty.span)
-                                } else {
-                                    None
-                                }
-                            }),
+                            if let Some(name_args) = name.args
+                                && let [GenericArg::Type(ty), ..] = name_args.args
+                            {
+                                Some(ty.span)
+                            } else {
+                                None
+                            },
                             args.type_at(0),
                         ),
                     ),
@@ -432,33 +431,29 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
                         DerefTy::Path,
                     ),
                     Some(sym::Cow) if mutability == Mutability::Not => {
-                        if let Some((lifetime, ty)) = name.args.and_then(|args| {
-                            if let [GenericArg::Lifetime(lifetime), ty] = args.args {
-                                return Some((lifetime, ty));
-                            }
-                            None
-                        }) {
+                        if let Some(name_args) = name.args
+                            && let [GenericArg::Lifetime(lifetime), ty] = name_args.args
+                        {
                             if let LifetimeKind::Param(param_def_id) = lifetime.kind
                                 && !lifetime.is_anonymous()
                                 && fn_sig
                                     .output()
                                     .walk()
-                                    .filter_map(|arg| {
-                                        arg.as_region().and_then(|lifetime| match lifetime.kind() {
-                                            ty::ReEarlyParam(r) => Some(
-                                                cx.tcx
-                                                    .generics_of(cx.tcx.parent(param_def_id.to_def_id()))
-                                                    .region_param(r, cx.tcx)
-                                                    .def_id,
-                                            ),
-                                            ty::ReBound(_, r) => r.kind.get_id(),
-                                            ty::ReLateParam(r) => r.kind.get_id(),
-                                            ty::ReStatic
-                                            | ty::ReVar(_)
-                                            | ty::RePlaceholder(_)
-                                            | ty::ReErased
-                                            | ty::ReError(_) => None,
-                                        })
+                                    .filter_map(ty::GenericArg::as_region)
+                                    .filter_map(|lifetime| match lifetime.kind() {
+                                        ty::ReEarlyParam(r) => Some(
+                                            cx.tcx
+                                                .generics_of(cx.tcx.parent(param_def_id.to_def_id()))
+                                                .region_param(r, cx.tcx)
+                                                .def_id,
+                                        ),
+                                        ty::ReBound(_, r) => r.kind.get_id(),
+                                        ty::ReLateParam(r) => r.kind.get_id(),
+                                        ty::ReStatic
+                                        | ty::ReVar(_)
+                                        | ty::RePlaceholder(_)
+                                        | ty::ReErased
+                                        | ty::ReError(_) => None,
                                     })
                                     .any(|def_id| def_id.as_local().is_some_and(|def_id| def_id == param_def_id))
                             {
@@ -627,12 +622,16 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[
                         }
                     }
 
+                    // If the expression's type gets adjusted down to the deref type, we might as
+                    // well have started with that deref type -- the lint should fire
                     let deref_ty = args.deref_ty.ty(self.cx);
                     let adjusted_ty = self.cx.typeck_results().expr_ty_adjusted(e).peel_refs();
                     if adjusted_ty == deref_ty {
                         return;
                     }
 
+                    // If the expression's type is constrained by `dyn Trait`, see if the deref
+                    // type implements the trait(s) as well, and if so, the lint should fire
                     if let ty::Dynamic(preds, ..) = adjusted_ty.kind()
                         && matches_preds(self.cx, deref_ty, preds)
                     {
diff --git a/src/tools/clippy/clippy_lints/src/raw_strings.rs b/src/tools/clippy/clippy_lints/src/raw_strings.rs
index 6a79cae32a5..943e662479e 100644
--- a/src/tools/clippy/clippy_lints/src/raw_strings.rs
+++ b/src/tools/clippy/clippy_lints/src/raw_strings.rs
@@ -103,15 +103,7 @@ impl EarlyLintPass for RawStrings {
 }
 
 impl RawStrings {
-    fn check_raw_string(
-        &mut self,
-        cx: &EarlyContext<'_>,
-        str: &str,
-        lit_span: Span,
-        prefix: &str,
-        max: u8,
-        descr: &str,
-    ) {
+    fn check_raw_string(&self, cx: &EarlyContext<'_>, str: &str, lit_span: Span, prefix: &str, max: u8, descr: &str) {
         if !str.contains(['\\', '"']) {
             span_lint_and_then(
                 cx,
diff --git a/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs b/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs
index 902e8af7ec4..0c1c664f111 100644
--- a/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs
+++ b/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs
@@ -88,8 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate {
 // We ignore macro exports. And `ListStem` uses, which aren't interesting.
 fn is_ignorable_export<'tcx>(item: &'tcx Item<'tcx>) -> bool {
     if let ItemKind::Use(path, kind) = item.kind {
-        let ignore = matches!(path.res.macro_ns, Some(Res::Def(DefKind::Macro(_), _)))
-            || kind == UseKind::ListStem;
+        let ignore = matches!(path.res.macro_ns, Some(Res::Def(DefKind::Macro(_), _))) || kind == UseKind::ListStem;
         if ignore {
             return true;
         }
diff --git a/src/tools/clippy/clippy_lints/src/reference.rs b/src/tools/clippy/clippy_lints/src/reference.rs
index 4bff37216ed..3bbcad12a31 100644
--- a/src/tools/clippy/clippy_lints/src/reference.rs
+++ b/src/tools/clippy/clippy_lints/src/reference.rs
@@ -1,10 +1,11 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::{SpanRangeExt, snippet_with_applicability};
-use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp};
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::{Sugg, has_enclosing_paren};
+use clippy_utils::ty::adjust_derefs_manually_drop;
 use rustc_errors::Applicability;
-use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_hir::{Expr, ExprKind, HirId, Node, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::declare_lint_pass;
-use rustc_span::{BytePos, Span};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -37,17 +38,12 @@ declare_clippy_lint! {
 
 declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]);
 
-fn without_parens(mut e: &Expr) -> &Expr {
-    while let ExprKind::Paren(ref child_e) = e.kind {
-        e = child_e;
-    }
-    e
-}
-
-impl EarlyLintPass for DerefAddrOf {
-    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
-        if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind
-            && let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind
+impl LateLintPass<'_> for DerefAddrOf {
+    fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
+        if !e.span.from_expansion()
+            && let ExprKind::Unary(UnOp::Deref, deref_target) = e.kind
+            && !deref_target.span.from_expansion()
+            && let ExprKind::AddrOf(_, _, addrof_target) = deref_target.kind
             // NOTE(tesuji): `*&` forces rustc to const-promote the array to `.rodata` section.
             // See #12854 for details.
             && !matches!(addrof_target.kind, ExprKind::Array(_))
@@ -55,57 +51,82 @@ impl EarlyLintPass for DerefAddrOf {
             && !addrof_target.span.from_expansion()
         {
             let mut applicability = Applicability::MachineApplicable;
-            let sugg = if e.span.from_expansion() {
-                if let Some(macro_source) = e.span.get_source_text(cx) {
-                    // Remove leading whitespace from the given span
-                    // e.g: ` $visitor` turns into `$visitor`
-                    let trim_leading_whitespaces = |span: Span| {
-                        span.get_source_text(cx)
-                            .and_then(|snip| {
-                                #[expect(clippy::cast_possible_truncation)]
-                                snip.find(|c: char| !c.is_whitespace())
-                                    .map(|pos| span.lo() + BytePos(pos as u32))
-                            })
-                            .map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace))
-                    };
+            let mut sugg = || Sugg::hir_with_applicability(cx, addrof_target, "_", &mut applicability);
 
-                    let mut generate_snippet = |pattern: &str| {
-                        #[expect(clippy::cast_possible_truncation)]
-                        macro_source.rfind(pattern).map(|pattern_pos| {
-                            let rpos = pattern_pos + pattern.len();
-                            let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32));
-                            let span = trim_leading_whitespaces(span_after_ref);
-                            snippet_with_applicability(cx, span, "_", &mut applicability)
-                        })
-                    };
+            // If this expression is an explicit `DerefMut` of a `ManuallyDrop` reached through a
+            // union, we may remove the reference if we are at the point where the implicit
+            // dereference would take place. Otherwise, we should not lint.
+            let sugg = match is_manually_drop_through_union(cx, e.hir_id, addrof_target) {
+                ManuallyDropThroughUnion::Directly => sugg().deref(),
+                ManuallyDropThroughUnion::Indirect => return,
+                ManuallyDropThroughUnion::No => sugg(),
+            };
+
+            let sugg = if has_enclosing_paren(snippet(cx, e.span, "")) {
+                sugg.maybe_paren()
+            } else {
+                sugg
+            };
+
+            span_lint_and_sugg(
+                cx,
+                DEREF_ADDROF,
+                e.span,
+                "immediately dereferencing a reference",
+                "try",
+                sugg.to_string(),
+                applicability,
+            );
+        }
+    }
+}
+
+/// Is this a `ManuallyDrop` reached through a union, and when is `DerefMut` called on it?
+enum ManuallyDropThroughUnion {
+    /// `ManuallyDrop` reached through a union and immediately explicitely dereferenced
+    Directly,
+    /// `ManuallyDrop` reached through a union, and dereferenced later on
+    Indirect,
+    /// Any other situation
+    No,
+}
 
-                    if *mutability == Mutability::Mut {
-                        generate_snippet("mut")
+/// Check if `addrof_target` is part of an access to a `ManuallyDrop` entity reached through a
+/// union, and when it is dereferenced using `DerefMut` starting from `expr_id` and going up.
+fn is_manually_drop_through_union(
+    cx: &LateContext<'_>,
+    expr_id: HirId,
+    addrof_target: &Expr<'_>,
+) -> ManuallyDropThroughUnion {
+    if is_reached_through_union(cx, addrof_target) {
+        let typeck = cx.typeck_results();
+        for (idx, id) in std::iter::once(expr_id)
+            .chain(cx.tcx.hir_parent_id_iter(expr_id))
+            .enumerate()
+        {
+            if let Node::Expr(expr) = cx.tcx.hir_node(id) {
+                if adjust_derefs_manually_drop(typeck.expr_adjustments(expr), typeck.expr_ty(expr)) {
+                    return if idx == 0 {
+                        ManuallyDropThroughUnion::Directly
                     } else {
-                        generate_snippet("&")
-                    }
-                } else {
-                    Some(snippet_with_applicability(cx, e.span, "_", &mut applicability))
+                        ManuallyDropThroughUnion::Indirect
+                    };
                 }
             } else {
-                Some(snippet_with_applicability(
-                    cx,
-                    addrof_target.span,
-                    "_",
-                    &mut applicability,
-                ))
-            };
-            if let Some(sugg) = sugg {
-                span_lint_and_sugg(
-                    cx,
-                    DEREF_ADDROF,
-                    e.span,
-                    "immediately dereferencing a reference",
-                    "try",
-                    sugg.to_string(),
-                    applicability,
-                );
+                break;
             }
         }
     }
+    ManuallyDropThroughUnion::No
+}
+
+/// Checks whether `expr` denotes an object reached through a union
+fn is_reached_through_union(cx: &LateContext<'_>, mut expr: &Expr<'_>) -> bool {
+    while let ExprKind::Field(parent, _) | ExprKind::Index(parent, _, _) = expr.kind {
+        if cx.typeck_results().expr_ty_adjusted(parent).is_union() {
+            return true;
+        }
+        expr = parent;
+    }
+    false
 }
diff --git a/src/tools/clippy/clippy_lints/src/swap.rs b/src/tools/clippy/clippy_lints/src/swap.rs
index 5ecbb56925e..76ab3cdae22 100644
--- a/src/tools/clippy/clippy_lints/src/swap.rs
+++ b/src/tools/clippy/clippy_lints/src/swap.rs
@@ -380,7 +380,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> {
         }
     }
 
-    fn is_used_other_than_swapping(&mut self, idx_ident: Ident) -> bool {
+    fn is_used_other_than_swapping(&self, idx_ident: Ident) -> bool {
         if Self::is_used_slice_indexed(self.swap1_idx, idx_ident)
             || Self::is_used_slice_indexed(self.swap2_idx, idx_ident)
         {
@@ -389,7 +389,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> {
         self.is_used_after_swap(idx_ident)
     }
 
-    fn is_used_after_swap(&mut self, idx_ident: Ident) -> bool {
+    fn is_used_after_swap(&self, idx_ident: Ident) -> bool {
         let mut v = IndexBindingVisitor {
             idx: idx_ident,
             suggest_span: self.suggest_span,
diff --git a/src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs b/src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs
index 535c044f49e..97e68b3df94 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::{eq_expr_value, path_to_local, sym};
+use clippy_utils::{eq_expr_value, path_to_local_with_projections, sym};
 use rustc_abi::WrappingRange;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, Node};
@@ -63,11 +63,7 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_
 /// Checks if an expression is a path to a local variable (with optional projections), e.g.
 /// `x.field[0].field2` would return true.
 fn is_local_with_projections(expr: &Expr<'_>) -> bool {
-    match expr.kind {
-        ExprKind::Path(_) => path_to_local(expr).is_some(),
-        ExprKind::Field(expr, _) | ExprKind::Index(expr, ..) => is_local_with_projections(expr),
-        _ => false,
-    }
+    path_to_local_with_projections(expr).is_some()
 }
 
 pub(super) fn check<'tcx>(
diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs
index d5112e2c3f9..1c7bb4314dd 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs
@@ -105,7 +105,7 @@ declare_clippy_lint! {
     /// ```
     #[clippy::version = "pre 1.29.0"]
     pub CROSSPOINTER_TRANSMUTE,
-    complexity,
+    suspicious,
     "transmutes that have to or from types that are a pointer to the other"
 }
 
diff --git a/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs b/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs
index ec5fb2793f9..b898920baef 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs
@@ -49,17 +49,7 @@ pub(super) fn check<'tcx>(
             true
         },
         (ty::Int(_) | ty::Uint(_), ty::RawPtr(_, _)) => {
-            span_lint_and_then(
-                cx,
-                USELESS_TRANSMUTE,
-                e.span,
-                "transmute from an integer to a pointer",
-                |diag| {
-                    if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
-                        diag.span_suggestion(e.span, "try", arg.as_ty(to_ty.to_string()), Applicability::Unspecified);
-                    }
-                },
-            );
+            // Handled by the upstream rustc `integer_to_ptr_transmutes` lint
             true
         },
         _ => false,
diff --git a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
index 1c52de52619..ba0d4de5f3b 100644
--- a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
+++ b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
@@ -202,79 +202,41 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
         };
 
         let item_has_safety_comment = item_has_safety_comment(cx, item);
-        match (&item.kind, item_has_safety_comment) {
-            // lint unsafe impl without safety comment
-            (ItemKind::Impl(Impl { of_trait: Some(of_trait), .. }), HasSafetyComment::No) if of_trait.safety.is_unsafe() => {
-                if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
-                    && !is_unsafe_from_proc_macro(cx, item.span)
-                {
-                    let source_map = cx.tcx.sess.source_map();
-                    let span = if source_map.is_multiline(item.span) {
-                        source_map.span_until_char(item.span, '\n')
-                    } else {
-                        item.span
-                    };
-
-                    #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
-                    span_lint_and_then(
-                        cx,
-                        UNDOCUMENTED_UNSAFE_BLOCKS,
-                        span,
-                        "unsafe impl missing a safety comment",
-                        |diag| {
-                            diag.help("consider adding a safety comment on the preceding line");
-                        },
-                    );
-                }
-            },
-            // lint safe impl with unnecessary safety comment
-            (ItemKind::Impl(Impl { of_trait: Some(of_trait), .. }), HasSafetyComment::Yes(pos)) if of_trait.safety.is_safe() => {
-                if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
-                    let (span, help_span) = mk_spans(pos);
-
-                    span_lint_and_then(
-                        cx,
-                        UNNECESSARY_SAFETY_COMMENT,
-                        span,
-                        "impl has unnecessary safety comment",
-                        |diag| {
-                            diag.span_help(help_span, "consider removing the safety comment");
-                        },
-                    );
-                }
-            },
-            (ItemKind::Impl(_), _) => {},
-            // const and static items only need a safety comment if their body is an unsafe block, lint otherwise
-            (&ItemKind::Const(.., body) | &ItemKind::Static(.., body), HasSafetyComment::Yes(pos)) => {
-                if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) {
-                    let body = cx.tcx.hir_body(body);
-                    if !matches!(
-                        body.value.kind, hir::ExprKind::Block(block, _)
-                        if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
-                    ) {
-                        let (span, help_span) = mk_spans(pos);
-
-                        span_lint_and_then(
-                            cx,
-                            UNNECESSARY_SAFETY_COMMENT,
-                            span,
-                            format!(
-                                "{} has unnecessary safety comment",
-                                cx.tcx.def_descr(item.owner_id.to_def_id()),
-                            ),
-                            |diag| {
-                                diag.span_help(help_span, "consider removing the safety comment");
-                            },
-                        );
-                    }
-                }
-            },
-            // Aside from unsafe impls and consts/statics with an unsafe block, items in general
-            // do not have safety invariants that need to be documented, so lint those.
-            (_, HasSafetyComment::Yes(pos)) => {
-                if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
-                    let (span, help_span) = mk_spans(pos);
+        match item_has_safety_comment {
+            HasSafetyComment::Yes(pos) => check_has_safety_comment(cx, item, mk_spans(pos)),
+            HasSafetyComment::No => check_has_no_safety_comment(cx, item),
+            HasSafetyComment::Maybe => {},
+        }
+    }
+}
 
+fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span)) {
+    match &item.kind {
+        ItemKind::Impl(Impl {
+            of_trait: Some(of_trait),
+            ..
+        }) if of_trait.safety.is_safe() => {
+            if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
+                span_lint_and_then(
+                    cx,
+                    UNNECESSARY_SAFETY_COMMENT,
+                    span,
+                    "impl has unnecessary safety comment",
+                    |diag| {
+                        diag.span_help(help_span, "consider removing the safety comment");
+                    },
+                );
+            }
+        },
+        ItemKind::Impl(_) => {},
+        // const and static items only need a safety comment if their body is an unsafe block, lint otherwise
+        &ItemKind::Const(.., body) | &ItemKind::Static(.., body) => {
+            if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) {
+                let body = cx.tcx.hir_body(body);
+                if !matches!(
+                    body.value.kind, hir::ExprKind::Block(block, _)
+                    if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
+                ) {
                     span_lint_and_then(
                         cx,
                         UNNECESSARY_SAFETY_COMMENT,
@@ -288,12 +250,56 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
                         },
                     );
                 }
-            },
-            _ => (),
-        }
+            }
+        },
+        // Aside from unsafe impls and consts/statics with an unsafe block, items in general
+        // do not have safety invariants that need to be documented, so lint those.
+        _ => {
+            if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
+                span_lint_and_then(
+                    cx,
+                    UNNECESSARY_SAFETY_COMMENT,
+                    span,
+                    format!(
+                        "{} has unnecessary safety comment",
+                        cx.tcx.def_descr(item.owner_id.to_def_id()),
+                    ),
+                    |diag| {
+                        diag.span_help(help_span, "consider removing the safety comment");
+                    },
+                );
+            }
+        },
     }
 }
+fn check_has_no_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) {
+    if let ItemKind::Impl(Impl {
+        of_trait: Some(of_trait),
+        ..
+    }) = item.kind
+        && of_trait.safety.is_unsafe()
+        && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
+        && !is_unsafe_from_proc_macro(cx, item.span)
+    {
+        let source_map = cx.tcx.sess.source_map();
+        let span = if source_map.is_multiline(item.span) {
+            source_map.span_until_char(item.span, '\n')
+        } else {
+            item.span
+        };
 
+        #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
+        span_lint_and_then(
+            cx,
+            UNDOCUMENTED_UNSAFE_BLOCKS,
+            span,
+            "unsafe impl missing a safety comment",
+            |diag| {
+                diag.help("consider adding a safety comment on the preceding line");
+            },
+        );
+    }
+}
 fn expr_has_unnecessary_safety_comment<'tcx>(
     cx: &LateContext<'tcx>,
     expr: &'tcx hir::Expr<'tcx>,
@@ -505,7 +511,8 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
         },
         Node::Stmt(stmt) => {
             if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) {
-                walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
+                walk_span_to_context(block.span, SyntaxContext::root())
+                    .map(|sp| CommentStartBeforeItem::Offset(sp.lo()))
             } else {
                 // Problem getting the parent node. Pretend a comment was found.
                 return HasSafetyComment::Maybe;
@@ -518,10 +525,12 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
     };
 
     let source_map = cx.sess().source_map();
+    // If the comment is in the first line of the file, there is no preceding line
     if let Some(comment_start) = comment_start
         && let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
-        && let Ok(comment_start_line) = source_map.lookup_line(comment_start)
-        && Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
+        && let Ok(comment_start_line) = source_map.lookup_line(comment_start.into())
+        && let include_first_line_of_file = matches!(comment_start, CommentStartBeforeItem::Start)
+        && (include_first_line_of_file || Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf))
         && let Some(src) = unsafe_line.sf.src.as_deref()
     {
         return if comment_start_line.line >= unsafe_line.line {
@@ -529,7 +538,8 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
         } else {
             match text_has_safety_comment(
                 src,
-                &unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
+                &unsafe_line.sf.lines()
+                    [(comment_start_line.line + usize::from(!include_first_line_of_file))..=unsafe_line.line],
                 unsafe_line.sf.start_pos,
             ) {
                 Some(b) => HasSafetyComment::Yes(b),
@@ -592,12 +602,27 @@ fn stmt_has_safety_comment(
     HasSafetyComment::Maybe
 }
 
+#[derive(Clone, Copy, Debug)]
+enum CommentStartBeforeItem {
+    Offset(BytePos),
+    Start,
+}
+
+impl From<CommentStartBeforeItem> for BytePos {
+    fn from(value: CommentStartBeforeItem) -> Self {
+        match value {
+            CommentStartBeforeItem::Offset(loc) => loc,
+            CommentStartBeforeItem::Start => BytePos(0),
+        }
+    }
+}
+
 fn comment_start_before_item_in_mod(
     cx: &LateContext<'_>,
     parent_mod: &hir::Mod<'_>,
     parent_mod_span: Span,
     item: &hir::Item<'_>,
-) -> Option<BytePos> {
+) -> Option<CommentStartBeforeItem> {
     parent_mod.item_ids.iter().enumerate().find_map(|(idx, item_id)| {
         if *item_id == item.item_id() {
             if idx == 0 {
@@ -605,15 +630,18 @@ fn comment_start_before_item_in_mod(
                 // ^------------------------------------------^ returns the start of this span
                 // ^---------------------^ finally checks comments in this range
                 if let Some(sp) = walk_span_to_context(parent_mod_span, SyntaxContext::root()) {
-                    return Some(sp.lo());
+                    return Some(CommentStartBeforeItem::Offset(sp.lo()));
                 }
             } else {
                 // some_item /* comment */ unsafe impl T {}
                 // ^-------^ returns the end of this span
                 //         ^---------------^ finally checks comments in this range
                 let prev_item = cx.tcx.hir_item(parent_mod.item_ids[idx - 1]);
+                if prev_item.span.is_dummy() {
+                    return Some(CommentStartBeforeItem::Start);
+                }
                 if let Some(sp) = walk_span_to_context(prev_item.span, SyntaxContext::root()) {
-                    return Some(sp.hi());
+                    return Some(CommentStartBeforeItem::Offset(sp.hi()));
                 }
             }
         }
@@ -668,7 +696,7 @@ fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
             }) => {
                 return maybe_mod_item
                     .and_then(|item| comment_start_before_item_in_mod(cx, mod_, *span, &item))
-                    .map(|comment_start| mod_.spans.inner_span.with_lo(comment_start))
+                    .map(|comment_start| mod_.spans.inner_span.with_lo(comment_start.into()))
                     .or(Some(*span));
             },
             node if let Some((span, _)) = span_and_hid_of_item_alike_node(&node)
diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs b/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs
index 2b7d3dc0c90..6e3e41f08ee 100644
--- a/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs
+++ b/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs
@@ -55,7 +55,7 @@ impl UnnecessaryBoxReturns {
         }
     }
 
-    fn check_fn_item(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) {
+    fn check_fn_item(&self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) {
         // we don't want to tell someone to break an exported function if they ask us not to
         if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
             return;
diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs b/src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs
index f1d1a76d0c2..76e24b6bf80 100644
--- a/src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs
+++ b/src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs
@@ -86,7 +86,9 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon {
                 expr.kind,
                 ExprKind::If(..) | ExprKind::Match(_, _, MatchSource::Normal | MatchSource::Postfix)
             )
-            && cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit
+            && cx.typeck_results().expr_ty(expr).is_unit()
+            // if a stmt has attrs, then turning it into an expr will break the code, since attrs aren't allowed on exprs
+            && cx.tcx.hir_attrs(stmt.hir_id).is_empty()
         {
             if let Some(block_is_unit) = self.is_last_in_block(stmt) {
                 if cx.tcx.sess.edition() <= Edition2021 && leaks_droppable_temporary_with_limited_lifetime(cx, expr) {
diff --git a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
index e9ad578da2f..8b278d98a30 100644
--- a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
+++ b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
@@ -284,14 +284,14 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec<Box<Pat>>, focus_idx:
             |k, ps1, idx| matches!(
                 k,
                 TupleStruct(qself2, path2, ps2)
-                    if eq_maybe_qself(qself1.as_ref(), qself2.as_ref())
+                    if eq_maybe_qself(qself1.as_deref(), qself2.as_deref())
                        && eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx)
             ),
             |k| always_pat!(k, TupleStruct(_, _, ps) => ps),
         ),
         // Transform a record pattern `S { fp_0, ..., fp_n }`.
         Struct(qself1, path1, fps1, rest1) => {
-            extend_with_struct_pat(qself1.as_ref(), path1, fps1, *rest1, start, alternatives)
+            extend_with_struct_pat(qself1.as_deref(), path1, fps1, *rest1, start, alternatives)
         },
     };
 
@@ -304,7 +304,7 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec<Box<Pat>>, focus_idx:
 /// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern
 /// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal.
 fn extend_with_struct_pat(
-    qself1: Option<&Box<ast::QSelf>>,
+    qself1: Option<&ast::QSelf>,
     path1: &ast::Path,
     fps1: &mut [ast::PatField],
     rest1: ast::PatFieldsRest,
@@ -319,7 +319,7 @@ fn extend_with_struct_pat(
             |k| {
                 matches!(k, Struct(qself2, path2, fps2, rest2)
                 if rest1 == *rest2 // If one struct pattern has `..` so must the other.
-                && eq_maybe_qself(qself1, qself2.as_ref())
+                && eq_maybe_qself(qself1, qself2.as_deref())
                 && eq_path(path1, path2)
                 && fps1.len() == fps2.len()
                 && fps1.iter().enumerate().all(|(idx_1, fp1)| {
diff --git a/src/tools/clippy/clippy_lints/src/unwrap.rs b/src/tools/clippy/clippy_lints/src/unwrap.rs
index c641d4e55b9..490da4f1e03 100644
--- a/src/tools/clippy/clippy_lints/src/unwrap.rs
+++ b/src/tools/clippy/clippy_lints/src/unwrap.rs
@@ -141,43 +141,45 @@ fn collect_unwrap_info<'tcx>(
         is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok)
     }
 
-    if let ExprKind::Binary(op, left, right) = &expr.kind {
-        match (invert, op.node) {
-            (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => {
-                let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false);
-                unwrap_info.append(&mut collect_unwrap_info(cx, if_expr, right, branch, invert, false));
-                return unwrap_info;
-            },
-            _ => (),
-        }
-    } else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind {
-        return collect_unwrap_info(cx, if_expr, expr, branch, !invert, false);
-    } else if let ExprKind::MethodCall(method_name, receiver, [], _) = &expr.kind
-        && let Some(local_id) = path_to_local(receiver)
-        && let ty = cx.typeck_results().expr_ty(receiver)
-        && let name = method_name.ident.name
-        && (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name))
-    {
-        let unwrappable = matches!(name, sym::is_some | sym::is_ok);
-        let safe_to_unwrap = unwrappable != invert;
-        let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
-            UnwrappableKind::Option
-        } else {
-            UnwrappableKind::Result
-        };
+    match expr.kind {
+        ExprKind::Binary(op, left, right)
+            if matches!(
+                (invert, op.node),
+                (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr)
+            ) =>
+        {
+            let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false);
+            unwrap_info.extend(collect_unwrap_info(cx, if_expr, right, branch, invert, false));
+            unwrap_info
+        },
+        ExprKind::Unary(UnOp::Not, expr) => collect_unwrap_info(cx, if_expr, expr, branch, !invert, false),
+        ExprKind::MethodCall(method_name, receiver, [], _)
+            if let Some(local_id) = path_to_local(receiver)
+                && let ty = cx.typeck_results().expr_ty(receiver)
+                && let name = method_name.ident.name
+                && (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name)) =>
+        {
+            let unwrappable = matches!(name, sym::is_some | sym::is_ok);
+            let safe_to_unwrap = unwrappable != invert;
+            let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
+                UnwrappableKind::Option
+            } else {
+                UnwrappableKind::Result
+            };
 
-        return vec![UnwrapInfo {
-            local_id,
-            if_expr,
-            check: expr,
-            check_name: name,
-            branch,
-            safe_to_unwrap,
-            kind,
-            is_entire_condition,
-        }];
+            vec![UnwrapInfo {
+                local_id,
+                if_expr,
+                check: expr,
+                check_name: name,
+                branch,
+                safe_to_unwrap,
+                kind,
+                is_entire_condition,
+            }]
+        },
+        _ => vec![],
     }
-    Vec::new()
 }
 
 /// A HIR visitor delegate that checks if a local variable of type `Option` or `Result` is mutated,
diff --git a/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs b/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs
index 1550872bca2..f1572fd65bb 100644
--- a/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs
+++ b/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs
@@ -56,6 +56,9 @@ impl LateLintPass<'_> for ZeroSizedMapValues {
             // cannot check if it is `Sized` or not, such as an incomplete associated type in a
             // type alias. See an example in `issue14822()` of `tests/ui/zero_sized_hashmap_values.rs`.
             && !ty.has_non_region_param()
+            // Ensure that no region escapes to avoid an assertion error when computing the layout.
+            // See an example in `issue15429()` of `tests/ui/zero_sized_hashmap_values.rs`.
+            && !ty.has_escaping_bound_vars()
             && let Ok(layout) = cx.layout_of(ty)
             && layout.is_zst()
         {
diff --git a/src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs b/src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs
index 5e6a40ac2eb..0fd1e11b033 100644
--- a/src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs
+++ b/src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs
@@ -6,7 +6,8 @@ use rustc_hir::attrs::AttributeKind;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::LocalDefId;
 use rustc_hir::{
-    AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitRef, Ty, TyKind, find_attr,
+    AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitImplHeader, TraitRef, Ty,
+    TyKind, find_attr,
 };
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_lint_defs::declare_tool_lint;
@@ -56,10 +57,14 @@ impl<'tcx> LateLintPass<'tcx> for DeriveDeserializeAllowingUnknown {
         // Is this an `impl` (of a certain form)?
         let ItemKind::Impl(Impl {
             of_trait:
-                Some(TraitRef {
-                    path:
-                        Path {
-                            res: Res::Def(_, trait_def_id),
+                Some(TraitImplHeader {
+                    trait_ref:
+                        TraitRef {
+                            path:
+                                Path {
+                                    res: Res::Def(_, trait_def_id),
+                                    ..
+                                },
                             ..
                         },
                     ..
diff --git a/src/tools/clippy/clippy_test_deps/Cargo.lock b/src/tools/clippy/clippy_test_deps/Cargo.lock
index 2f987c0137c..b22cf9d107d 100644
--- a/src/tools/clippy/clippy_test_deps/Cargo.lock
+++ b/src/tools/clippy/clippy_test_deps/Cargo.lock
@@ -377,9 +377,9 @@ dependencies = [
 
 [[package]]
 name = "slab"
-version = "0.4.10"
+version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
 
 [[package]]
 name = "smallvec"
diff --git a/src/tools/clippy/clippy_utils/README.md b/src/tools/clippy/clippy_utils/README.md
index 6d8dd92d55d..2dfe28953d0 100644
--- a/src/tools/clippy/clippy_utils/README.md
+++ b/src/tools/clippy/clippy_utils/README.md
@@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
 
 <!-- begin autogenerated nightly -->
 ```
-nightly-2025-08-07
+nightly-2025-08-22
 ```
 <!-- end autogenerated nightly -->
 
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs
index 24e017f7cf7..40c00568a3b 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs
@@ -41,21 +41,23 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
             b1 == b2 && eq_id(*i1, *i2) && both(s1.as_deref(), s2.as_deref(), eq_pat)
         },
         (Range(lf, lt, le), Range(rf, rt, re)) => {
-            eq_expr_opt(lf.as_ref(), rf.as_ref())
-                && eq_expr_opt(lt.as_ref(), rt.as_ref())
+            eq_expr_opt(lf.as_deref(), rf.as_deref())
+                && eq_expr_opt(lt.as_deref(), rt.as_deref())
                 && eq_range_end(&le.node, &re.node)
         },
         (Box(l), Box(r))
         | (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
         | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
         (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
-        (Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp),
+        (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp),
         (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => {
-            eq_maybe_qself(lqself.as_ref(), rqself.as_ref()) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r))
+            eq_maybe_qself(lqself.as_deref(), rqself.as_deref())
+                && eq_path(lp, rp)
+                && over(lfs, rfs, |l, r| eq_pat(l, r))
         },
         (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => {
             lr == rr
-                && eq_maybe_qself(lqself.as_ref(), rqself.as_ref())
+                && eq_maybe_qself(lqself.as_deref(), rqself.as_deref())
                 && eq_path(lp, rp)
                 && unordered_over(lfs, rfs, eq_field_pat)
         },
@@ -82,11 +84,11 @@ pub fn eq_field_pat(l: &PatField, r: &PatField) -> bool {
         && over(&l.attrs, &r.attrs, eq_attr)
 }
 
-pub fn eq_qself(l: &Box<QSelf>, r: &Box<QSelf>) -> bool {
+pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
     l.position == r.position && eq_ty(&l.ty, &r.ty)
 }
 
-pub fn eq_maybe_qself(l: Option<&Box<QSelf>>, r: Option<&Box<QSelf>>) -> bool {
+pub fn eq_maybe_qself(l: Option<&QSelf>, r: Option<&QSelf>) -> bool {
     match (l, r) {
         (Some(l), Some(r)) => eq_qself(l, r),
         (None, None) => true,
@@ -129,8 +131,8 @@ pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
     }
 }
 
-pub fn eq_expr_opt(l: Option<&Box<Expr>>, r: Option<&Box<Expr>>) -> bool {
-    both(l, r, |l, r| eq_expr(l, r))
+pub fn eq_expr_opt(l: Option<&Expr>, r: Option<&Expr>) -> bool {
+    both(l, r, eq_expr)
 }
 
 pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool {
@@ -177,7 +179,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
         (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
         (Let(lp, le, _, _), Let(rp, re, _, _)) => eq_pat(lp, rp) && eq_expr(le, re),
         (If(lc, lt, le), If(rc, rt, re)) => {
-            eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le.as_ref(), re.as_ref())
+            eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le.as_deref(), re.as_deref())
         },
         (While(lc, lt, ll), While(rc, rt, rl)) => {
             eq_label(ll.as_ref(), rl.as_ref()) && eq_expr(lc, rc) && eq_block(lt, rt)
@@ -201,9 +203,11 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
         (Loop(lt, ll, _), Loop(rt, rl, _)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lt, rt),
         (Block(lb, ll), Block(rb, rl)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lb, rb),
         (TryBlock(l), TryBlock(r)) => eq_block(l, r),
-        (Yield(l), Yield(r)) => eq_expr_opt(l.expr(), r.expr()) && l.same_kind(r),
-        (Ret(l), Ret(r)) => eq_expr_opt(l.as_ref(), r.as_ref()),
-        (Break(ll, le), Break(rl, re)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_ref(), re.as_ref()),
+        (Yield(l), Yield(r)) => eq_expr_opt(l.expr().map(Box::as_ref), r.expr().map(Box::as_ref)) && l.same_kind(r),
+        (Ret(l), Ret(r)) => eq_expr_opt(l.as_deref(), r.as_deref()),
+        (Break(ll, le), Break(rl, re)) => {
+            eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_deref(), re.as_deref())
+        },
         (Continue(ll), Continue(rl)) => eq_label(ll.as_ref(), rl.as_ref()),
         (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2, _), Index(r1, r2, _)) => {
             eq_expr(l1, r1) && eq_expr(l2, r2)
@@ -240,13 +244,13 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
         },
         (Gen(lc, lb, lk, _), Gen(rc, rb, rk, _)) => lc == rc && eq_block(lb, rb) && lk == rk,
         (Range(lf, lt, ll), Range(rf, rt, rl)) => {
-            ll == rl && eq_expr_opt(lf.as_ref(), rf.as_ref()) && eq_expr_opt(lt.as_ref(), rt.as_ref())
+            ll == rl && eq_expr_opt(lf.as_deref(), rf.as_deref()) && eq_expr_opt(lt.as_deref(), rt.as_deref())
         },
         (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
-        (Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp),
+        (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp),
         (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
         (Struct(lse), Struct(rse)) => {
-            eq_maybe_qself(lse.qself.as_ref(), rse.qself.as_ref())
+            eq_maybe_qself(lse.qself.as_deref(), rse.qself.as_deref())
                 && eq_path(&lse.path, &rse.path)
                 && eq_struct_rest(&lse.rest, &rse.rest)
                 && unordered_over(&lse.fields, &rse.fields, eq_field)
@@ -278,8 +282,8 @@ pub fn eq_field(l: &ExprField, r: &ExprField) -> bool {
 pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
     l.is_placeholder == r.is_placeholder
         && eq_pat(&l.pat, &r.pat)
-        && eq_expr_opt(l.body.as_ref(), r.body.as_ref())
-        && eq_expr_opt(l.guard.as_ref(), r.guard.as_ref())
+        && eq_expr_opt(l.body.as_deref(), r.body.as_deref())
+        && eq_expr_opt(l.guard.as_deref(), r.guard.as_deref())
         && over(&l.attrs, &r.attrs, eq_attr)
 }
 
@@ -324,7 +328,7 @@ pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> b
     over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
 }
 
-#[expect(clippy::similar_names, clippy::too_many_lines)] // Just a big match statement
+#[expect(clippy::too_many_lines)] // Just a big match statement
 pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
     use ItemKind::*;
     match (l, r) {
@@ -347,7 +351,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
                 safety: rs,
                 define_opaque: _,
             }),
-        ) => eq_id(*li, *ri) && lm == rm && ls == rs && eq_ty(lt, rt) && eq_expr_opt(le.as_ref(), re.as_ref()),
+        ) => eq_id(*li, *ri) && lm == rm && ls == rs && eq_ty(lt, rt) && eq_expr_opt(le.as_deref(), re.as_deref()),
         (
             Const(box ConstItem {
                 defaultness: ld,
@@ -370,7 +374,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
                 && eq_id(*li, *ri)
                 && eq_generics(lg, rg)
                 && eq_ty(lt, rt)
-                && eq_expr_opt(le.as_ref(), re.as_ref())
+                && eq_expr_opt(le.as_deref(), re.as_deref())
         },
         (
             Fn(box ast::Fn {
@@ -403,7 +407,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
             ls == rs
                 && eq_id(*li, *ri)
                 && match (lmk, rmk) {
-                    (ModKind::Loaded(litems, linline, _, _), ModKind::Loaded(ritems, rinline, _, _)) => {
+                    (ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => {
                         linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind))
                     },
                     (ModKind::Unloaded, ModKind::Unloaded) => true,
@@ -525,7 +529,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
                 safety: rs,
                 define_opaque: _,
             }),
-        ) => eq_id(*li, *ri) && eq_ty(lt, rt) && lm == rm && eq_expr_opt(le.as_ref(), re.as_ref()) && ls == rs,
+        ) => eq_id(*li, *ri) && eq_ty(lt, rt) && lm == rm && eq_expr_opt(le.as_deref(), re.as_deref()) && ls == rs,
         (
             Fn(box ast::Fn {
                 defaultness: ld,
@@ -607,7 +611,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
                 && eq_id(*li, *ri)
                 && eq_generics(lg, rg)
                 && eq_ty(lt, rt)
-                && eq_expr_opt(le.as_ref(), re.as_ref())
+                && eq_expr_opt(le.as_deref(), re.as_deref())
         },
         (
             Fn(box ast::Fn {
@@ -723,7 +727,8 @@ pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
 pub fn eq_opt_fn_contract(l: &Option<Box<FnContract>>, r: &Option<Box<FnContract>>) -> bool {
     match (l, r) {
         (Some(l), Some(r)) => {
-            eq_expr_opt(l.requires.as_ref(), r.requires.as_ref()) && eq_expr_opt(l.ensures.as_ref(), r.ensures.as_ref())
+            eq_expr_opt(l.requires.as_deref(), r.requires.as_deref())
+                && eq_expr_opt(l.ensures.as_deref(), r.ensures.as_deref())
         },
         (None, None) => true,
         (Some(_), None) | (None, Some(_)) => false,
@@ -841,7 +846,7 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
                 && eq_fn_decl(&l.decl, &r.decl)
         },
         (Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
-        (Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp),
+        (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp),
         (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, eq_generic_bound),
         (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, eq_generic_bound),
         (Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
index e0c1b9d445a..c4a759e919b 100644
--- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
+++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
@@ -19,8 +19,8 @@ use rustc_ast::token::CommentKind;
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{
     Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl,
-    ImplItem, ImplItemKind, TraitImplHeader, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path,
-    QPath, Safety, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource,
+    ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety,
+    TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource,
 };
 use rustc_lint::{EarlyContext, LateContext, LintContext};
 use rustc_middle::ty::TyCtxt;
@@ -254,7 +254,10 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
         ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
         ItemKind::Trait(_, _, Safety::Unsafe, ..)
         | ItemKind::Impl(Impl {
-            of_trait: Some(TraitImplHeader { safety: Safety::Unsafe, .. }), ..
+            of_trait: Some(TraitImplHeader {
+                safety: Safety::Unsafe, ..
+            }),
+            ..
         }) => (Pat::Str("unsafe"), Pat::Str("}")),
         ItemKind::Trait(_, IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")),
         ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")),
diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs
index 625e1eead21..8a19039a7fe 100644
--- a/src/tools/clippy/clippy_utils/src/diagnostics.rs
+++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs
@@ -22,13 +22,14 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) {
     {
         diag.help(format!(
             "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}",
-            &option_env!("RUST_RELEASE_NUM").map_or_else(
-                || "master".to_string(),
-                |n| {
-                    // extract just major + minor version and ignore patch versions
-                    format!("rust-{}", n.rsplit_once('.').unwrap().1)
-                }
-            )
+            match option_env!("CFG_RELEASE_CHANNEL") {
+                // Clippy version is 0.1.xx
+                //
+                // Always use .0 because we do not generate separate lint doc pages for rust patch releases
+                Some("stable") => concat!("rust-1.", env!("CARGO_PKG_VERSION_PATCH"), ".0"),
+                Some("beta") => "beta",
+                _ => "master",
+            }
         ));
     }
 }
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index f0d7fb89c44..8160443f413 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -258,7 +258,7 @@ impl HirEqInterExpr<'_, '_, '_> {
         })
     }
 
-    fn should_ignore(&mut self, expr: &Expr<'_>) -> bool {
+    fn should_ignore(&self, expr: &Expr<'_>) -> bool {
         macro_backtrace(expr.span).last().is_some_and(|macro_call| {
             matches!(
                 self.inner.cx.tcx.get_diagnostic_name(macro_call.def_id),
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index fcc120656e3..8533fa85541 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -460,6 +460,23 @@ pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
     path_to_local(expr) == Some(id)
 }
 
+/// If the expression is a path to a local (with optional projections),
+/// returns the canonical `HirId` of the local.
+///
+/// For example, `x.field[0].field2` would return the `HirId` of `x`.
+pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option<HirId> {
+    match expr.kind {
+        ExprKind::Field(recv, _) | ExprKind::Index(recv, _, _) => path_to_local_with_projections(recv),
+        ExprKind::Path(QPath::Resolved(
+            _,
+            Path {
+                res: Res::Local(local), ..
+            },
+        )) => Some(*local),
+        _ => None,
+    }
+}
+
 pub trait MaybePath<'hir> {
     fn hir_id(&self) -> HirId;
     fn qpath_opt(&self) -> Option<&QPath<'hir>>;
diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs
index 89a83e2c48f..896d607fbcd 100644
--- a/src/tools/clippy/clippy_utils/src/msrvs.rs
+++ b/src/tools/clippy/clippy_utils/src/msrvs.rs
@@ -189,25 +189,25 @@ impl MsrvStack {
 fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
     let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym::msrv]));
 
-    if let Some(msrv_attr) = msrv_attrs.next() {
-        if let Some(duplicate) = msrv_attrs.next_back() {
-            sess.dcx()
-                .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
-                .with_span_note(msrv_attr.span(), "first definition found here")
-                .emit();
-        }
-
-        if let Some(msrv) = msrv_attr.value_str() {
-            if let Some(version) = parse_version(msrv) {
-                return Some(version);
-            }
+    let msrv_attr = msrv_attrs.next()?;
 
-            sess.dcx()
-                .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
-        } else {
-            sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
-        }
+    if let Some(duplicate) = msrv_attrs.next_back() {
+        sess.dcx()
+            .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
+            .with_span_note(msrv_attr.span(), "first definition found here")
+            .emit();
     }
 
-    None
+    let Some(msrv) = msrv_attr.value_str() else {
+        sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
+        return None;
+    };
+
+    let Some(version) = parse_version(msrv) else {
+        sess.dcx()
+            .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
+        return None;
+    };
+
+    Some(version)
 }
diff --git a/src/tools/clippy/clippy_utils/src/ty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/mod.rs
index d79773f8321..fafc1d07e51 100644
--- a/src/tools/clippy/clippy_utils/src/ty/mod.rs
+++ b/src/tools/clippy/clippy_utils/src/ty/mod.rs
@@ -18,6 +18,7 @@ use rustc_lint::LateContext;
 use rustc_middle::mir::ConstValue;
 use rustc_middle::mir::interpret::Scalar;
 use rustc_middle::traits::EvaluationResult;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment};
 use rustc_middle::ty::layout::ValidityRequirement;
 use rustc_middle::ty::{
     self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
@@ -31,7 +32,7 @@ use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
 use rustc_trait_selection::traits::{Obligation, ObligationCause};
 use std::assert_matches::debug_assert_matches;
 use std::collections::hash_map::Entry;
-use std::iter;
+use std::{iter, mem};
 
 use crate::path_res;
 use crate::paths::{PathNS, lookup_path_str};
@@ -1382,7 +1383,6 @@ pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
         || matches!(ty.kind(), ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::Vec, adt_def.did()))
 }
 
-/// Gets the index of a field by name.
 pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option<usize> {
     match *ty.kind() {
         ty::Adt(def, _) if def.is_union() || def.is_struct() => {
@@ -1392,3 +1392,11 @@ pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option<usize> {
         _ => None,
     }
 }
+
+/// Checks if the adjustments contain a mutable dereference of a `ManuallyDrop<_>`.
+pub fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool {
+    adjustments.iter().any(|a| {
+        let ty = mem::replace(&mut ty, a.target);
+        matches!(a.kind, Adjust::Deref(Some(op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty)
+    })
+}
diff --git a/src/tools/clippy/rust-toolchain.toml b/src/tools/clippy/rust-toolchain.toml
index ac51ec2d61b..5497e77e8ad 100644
--- a/src/tools/clippy/rust-toolchain.toml
+++ b/src/tools/clippy/rust-toolchain.toml
@@ -1,6 +1,6 @@
 [toolchain]
 # begin autogenerated nightly
-channel = "nightly-2025-08-07"
+channel = "nightly-2025-08-22"
 # end autogenerated nightly
 components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
 profile = "minimal"
diff --git a/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr b/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr
new file mode 100644
index 00000000000..59a7146ac90
--- /dev/null
+++ b/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr
@@ -0,0 +1,26 @@
+error: module has unnecessary safety comment
+ --> src/main.rs:2:1
+  |
+2 | mod x {}
+  | ^^^^^^^^
+  |
+help: consider removing the safety comment
+ --> src/main.rs:1:1
+  |
+1 | // SAFETY: ...
+  | ^^^^^^^^^^^^^^
+  = note: requested on the command line with `-D clippy::unnecessary-safety-comment`
+
+error: module has unnecessary safety comment
+ --> src/main.rs:5:1
+  |
+5 | mod y {}
+  | ^^^^^^^^
+  |
+help: consider removing the safety comment
+ --> src/main.rs:4:1
+  |
+4 | // SAFETY: ...
+  | ^^^^^^^^^^^^^^
+
+error: could not compile `undocumented_unsafe_blocks` (bin "undocumented_unsafe_blocks") due to 2 previous errors
diff --git a/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.toml b/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.toml
new file mode 100644
index 00000000000..36bb3472df0
--- /dev/null
+++ b/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.toml
@@ -0,0 +1,12 @@
+# Reproducing #14553 requires the `# Safety` comment to be in the first line of 
+# the file. Since `unnecessary_safety_comment` is not enabled by default, we
+# will set it up here.
+
+[package]
+name = "undocumented_unsafe_blocks"
+edition = "2024"
+publish = false
+version = "0.1.0"
+
+[lints.clippy]
+unnecessary_safety_comment = "deny"
diff --git a/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/src/main.rs
new file mode 100644
index 00000000000..5cafcff99dd
--- /dev/null
+++ b/src/tools/clippy/tests/ui-cargo/undocumented_unsafe_blocks/fail/src/main.rs
@@ -0,0 +1,7 @@
+// SAFETY: ...
+mod x {}
+
+// SAFETY: ...
+mod y {}
+
+fn main() {}
diff --git a/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr
index 14a49cb76c1..e856963c87d 100644
--- a/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr
+++ b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr
@@ -1,12 +1,8 @@
 error: this function has too many lines (2/1)
   --> tests/ui-toml/functions_maxlines/test.rs:19:1
    |
-LL | / fn too_many_lines() {
-LL | |
-LL | |     println!("This is bad.");
-LL | |     println!("This is bad.");
-LL | | }
-   | |_^
+LL | fn too_many_lines() {
+   | ^^^^^^^^^^^^^^^^^^^
    |
    = note: `-D clippy::too-many-lines` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::too_many_lines)]`
@@ -14,35 +10,20 @@ LL | | }
 error: this function has too many lines (4/1)
   --> tests/ui-toml/functions_maxlines/test.rs:26:1
    |
-LL | / async fn async_too_many_lines() {
-LL | |
-LL | |     println!("This is bad.");
-LL | |     println!("This is bad.");
-LL | | }
-   | |_^
+LL | async fn async_too_many_lines() {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: this function has too many lines (4/1)
   --> tests/ui-toml/functions_maxlines/test.rs:33:1
    |
-LL | / fn closure_too_many_lines() {
-LL | |
-LL | |     let _ = {
-LL | |         println!("This is bad.");
-LL | |         println!("This is bad.");
-LL | |     };
-LL | | }
-   | |_^
+LL | fn closure_too_many_lines() {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: this function has too many lines (2/1)
   --> tests/ui-toml/functions_maxlines/test.rs:56:1
    |
-LL | / fn comment_before_code() {
-LL | |
-LL | |     let _ = "test";
-LL | |     /* This comment extends to the front of
-LL | |     the code but this line should still count. */ let _ = 5;
-LL | | }
-   | |_^
+LL | fn comment_before_code() {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 4 previous errors
 
diff --git a/src/tools/clippy/tests/ui/as_ptr_cast_mut.fixed b/src/tools/clippy/tests/ui/as_ptr_cast_mut.fixed
new file mode 100644
index 00000000000..fe9c5dca5ba
--- /dev/null
+++ b/src/tools/clippy/tests/ui/as_ptr_cast_mut.fixed
@@ -0,0 +1,38 @@
+#![allow(unused)]
+#![warn(clippy::as_ptr_cast_mut)]
+#![allow(clippy::wrong_self_convention, clippy::unnecessary_cast)]
+
+struct MutPtrWrapper(Vec<u8>);
+impl MutPtrWrapper {
+    fn as_ptr(&mut self) -> *const u8 {
+        self.0.as_mut_ptr() as *const u8
+    }
+}
+
+struct Covariant<T>(*const T);
+impl<T> Covariant<T> {
+    fn as_ptr(self) -> *const T {
+        self.0
+    }
+}
+
+fn main() {
+    let mut string = String::new();
+    let _ = string.as_mut_ptr();
+    //~^ as_ptr_cast_mut
+
+    let _ = string.as_ptr() as *const i8;
+    let _ = string.as_mut_ptr();
+    let _ = string.as_mut_ptr() as *mut u8;
+    let _ = string.as_mut_ptr() as *const u8;
+
+    let nn = std::ptr::NonNull::new(4 as *mut u8).unwrap();
+    let _ = nn.as_ptr() as *mut u8;
+
+    let mut wrap = MutPtrWrapper(Vec::new());
+    let _ = wrap.as_ptr() as *mut u8;
+
+    let mut local = 4;
+    let ref_with_write_perm = Covariant(std::ptr::addr_of_mut!(local) as *const _);
+    let _ = ref_with_write_perm.as_ptr() as *mut u8;
+}
diff --git a/src/tools/clippy/tests/ui/as_ptr_cast_mut.rs b/src/tools/clippy/tests/ui/as_ptr_cast_mut.rs
index baf7279adc4..3f22c2058d0 100644
--- a/src/tools/clippy/tests/ui/as_ptr_cast_mut.rs
+++ b/src/tools/clippy/tests/ui/as_ptr_cast_mut.rs
@@ -1,7 +1,6 @@
 #![allow(unused)]
 #![warn(clippy::as_ptr_cast_mut)]
 #![allow(clippy::wrong_self_convention, clippy::unnecessary_cast)]
-//@no-rustfix: incorrect suggestion
 
 struct MutPtrWrapper(Vec<u8>);
 impl MutPtrWrapper {
@@ -22,9 +21,6 @@ fn main() {
     let _ = string.as_ptr() as *mut u8;
     //~^ as_ptr_cast_mut
 
-    let _: *mut i8 = string.as_ptr() as *mut _;
-    //~^ as_ptr_cast_mut
-
     let _ = string.as_ptr() as *const i8;
     let _ = string.as_mut_ptr();
     let _ = string.as_mut_ptr() as *mut u8;
diff --git a/src/tools/clippy/tests/ui/as_ptr_cast_mut.stderr b/src/tools/clippy/tests/ui/as_ptr_cast_mut.stderr
index b3fc223ccdb..fa9fb23e2d0 100644
--- a/src/tools/clippy/tests/ui/as_ptr_cast_mut.stderr
+++ b/src/tools/clippy/tests/ui/as_ptr_cast_mut.stderr
@@ -1,5 +1,5 @@
 error: casting the result of `as_ptr` to *mut u8
-  --> tests/ui/as_ptr_cast_mut.rs:22:13
+  --> tests/ui/as_ptr_cast_mut.rs:21:13
    |
 LL |     let _ = string.as_ptr() as *mut u8;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()`
@@ -7,11 +7,5 @@ LL |     let _ = string.as_ptr() as *mut u8;
    = note: `-D clippy::as-ptr-cast-mut` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::as_ptr_cast_mut)]`
 
-error: casting the result of `as_ptr` to *mut i8
-  --> tests/ui/as_ptr_cast_mut.rs:25:22
-   |
-LL |     let _: *mut i8 = string.as_ptr() as *mut _;
-   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()`
-
-error: aborting due to 2 previous errors
+error: aborting due to 1 previous error
 
diff --git a/src/tools/clippy/tests/ui/as_ptr_cast_mut_unfixable.rs b/src/tools/clippy/tests/ui/as_ptr_cast_mut_unfixable.rs
new file mode 100644
index 00000000000..a8f6b06bd4f
--- /dev/null
+++ b/src/tools/clippy/tests/ui/as_ptr_cast_mut_unfixable.rs
@@ -0,0 +1,16 @@
+//@no-rustfix
+#![allow(unused)]
+#![warn(clippy::as_ptr_cast_mut)]
+
+fn main() {
+    let mut string = String::new();
+
+    // the `*mut _` is actually necessary since it does two things at once:
+    // - changes the mutability (caught by the lint)
+    // - changes the type
+    //
+    // and so replacing this with `as_mut_ptr` removes the second thing,
+    // resulting in a type mismatch
+    let _: *mut i8 = string.as_ptr() as *mut _;
+    //~^ as_ptr_cast_mut
+}
diff --git a/src/tools/clippy/tests/ui/as_ptr_cast_mut_unfixable.stderr b/src/tools/clippy/tests/ui/as_ptr_cast_mut_unfixable.stderr
new file mode 100644
index 00000000000..c5bcad6f4df
--- /dev/null
+++ b/src/tools/clippy/tests/ui/as_ptr_cast_mut_unfixable.stderr
@@ -0,0 +1,11 @@
+error: casting the result of `as_ptr` to *mut i8
+  --> tests/ui/as_ptr_cast_mut_unfixable.rs:14:22
+   |
+LL |     let _: *mut i8 = string.as_ptr() as *mut _;
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()`
+   |
+   = note: `-D clippy::as-ptr-cast-mut` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::as_ptr_cast_mut)]`
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/clippy/tests/ui/borrow_as_ptr.fixed b/src/tools/clippy/tests/ui/borrow_as_ptr.fixed
index 3f6e5245b87..bfe826508f3 100644
--- a/src/tools/clippy/tests/ui/borrow_as_ptr.fixed
+++ b/src/tools/clippy/tests/ui/borrow_as_ptr.fixed
@@ -1,6 +1,9 @@
+//@aux-build:proc_macros.rs
 #![warn(clippy::borrow_as_ptr)]
 #![allow(clippy::useless_vec)]
 
+extern crate proc_macros;
+
 fn a() -> i32 {
     0
 }
@@ -53,3 +56,12 @@ fn issue_15141() {
     // Don't lint cast to dyn trait pointers
     let b = &a as *const dyn std::any::Any;
 }
+
+fn issue15389() {
+    proc_macros::with_span! {
+        span
+        let var = 0u32;
+        // Don't lint in proc-macros
+        let _ = &var as *const u32;
+    };
+}
diff --git a/src/tools/clippy/tests/ui/borrow_as_ptr.rs b/src/tools/clippy/tests/ui/borrow_as_ptr.rs
index 20f4f40e001..ce248f157c6 100644
--- a/src/tools/clippy/tests/ui/borrow_as_ptr.rs
+++ b/src/tools/clippy/tests/ui/borrow_as_ptr.rs
@@ -1,6 +1,9 @@
+//@aux-build:proc_macros.rs
 #![warn(clippy::borrow_as_ptr)]
 #![allow(clippy::useless_vec)]
 
+extern crate proc_macros;
+
 fn a() -> i32 {
     0
 }
@@ -53,3 +56,12 @@ fn issue_15141() {
     // Don't lint cast to dyn trait pointers
     let b = &a as *const dyn std::any::Any;
 }
+
+fn issue15389() {
+    proc_macros::with_span! {
+        span
+        let var = 0u32;
+        // Don't lint in proc-macros
+        let _ = &var as *const u32;
+    };
+}
diff --git a/src/tools/clippy/tests/ui/borrow_as_ptr.stderr b/src/tools/clippy/tests/ui/borrow_as_ptr.stderr
index b1fcce49403..b371b477a50 100644
--- a/src/tools/clippy/tests/ui/borrow_as_ptr.stderr
+++ b/src/tools/clippy/tests/ui/borrow_as_ptr.stderr
@@ -1,5 +1,5 @@
 error: borrow as raw pointer
-  --> tests/ui/borrow_as_ptr.rs:11:14
+  --> tests/ui/borrow_as_ptr.rs:14:14
    |
 LL |     let _p = &val as *const i32;
    |              ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of!(val)`
@@ -8,25 +8,25 @@ LL |     let _p = &val as *const i32;
    = help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]`
 
 error: borrow as raw pointer
-  --> tests/ui/borrow_as_ptr.rs:19:18
+  --> tests/ui/borrow_as_ptr.rs:22:18
    |
 LL |     let _p_mut = &mut val_mut as *mut i32;
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(val_mut)`
 
 error: borrow as raw pointer
-  --> tests/ui/borrow_as_ptr.rs:23:16
+  --> tests/ui/borrow_as_ptr.rs:26:16
    |
 LL |     let _raw = (&mut x[1] as *mut i32).wrapping_offset(-1);
    |                ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(x[1])`
 
 error: borrow as raw pointer
-  --> tests/ui/borrow_as_ptr.rs:29:17
+  --> tests/ui/borrow_as_ptr.rs:32:17
    |
 LL |     let _raw = (&mut x[1] as *mut i32).wrapping_offset(-1);
    |                 ^^^^^^^^^^^^^^^^^^^^^ help: try: `&raw mut x[1]`
 
 error: implicit borrow as raw pointer
-  --> tests/ui/borrow_as_ptr.rs:35:25
+  --> tests/ui/borrow_as_ptr.rs:38:25
    |
 LL |     let p: *const i32 = &val;
    |                         ^^^^
@@ -37,7 +37,7 @@ LL |     let p: *const i32 = &raw const val;
    |                          +++++++++
 
 error: implicit borrow as raw pointer
-  --> tests/ui/borrow_as_ptr.rs:39:23
+  --> tests/ui/borrow_as_ptr.rs:42:23
    |
 LL |     let p: *mut i32 = &mut val;
    |                       ^^^^^^^^
@@ -48,7 +48,7 @@ LL |     let p: *mut i32 = &raw mut val;
    |                        +++
 
 error: implicit borrow as raw pointer
-  --> tests/ui/borrow_as_ptr.rs:44:19
+  --> tests/ui/borrow_as_ptr.rs:47:19
    |
 LL |     core::ptr::eq(&val, &1);
    |                   ^^^^
diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed b/src/tools/clippy/tests/ui/char_lit_as_u8.fixed
index 64aacedfd36..64aacedfd36 100644
--- a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed
+++ b/src/tools/clippy/tests/ui/char_lit_as_u8.fixed
diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8.rs b/src/tools/clippy/tests/ui/char_lit_as_u8.rs
index c8774c7f309..a8f39e27605 100644
--- a/src/tools/clippy/tests/ui/char_lit_as_u8.rs
+++ b/src/tools/clippy/tests/ui/char_lit_as_u8.rs
@@ -1,7 +1,12 @@
 #![warn(clippy::char_lit_as_u8)]
 
 fn main() {
-    // no suggestion, since a byte literal won't work.
-    let _ = '❤' as u8;
+    let _ = 'a' as u8;
+    //~^ char_lit_as_u8
+    let _ = '\n' as u8;
+    //~^ char_lit_as_u8
+    let _ = '\0' as u8;
+    //~^ char_lit_as_u8
+    let _ = '\x01' as u8;
     //~^ char_lit_as_u8
 }
diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8.stderr b/src/tools/clippy/tests/ui/char_lit_as_u8.stderr
index ec02f1341c0..9bcded7b0ff 100644
--- a/src/tools/clippy/tests/ui/char_lit_as_u8.stderr
+++ b/src/tools/clippy/tests/ui/char_lit_as_u8.stderr
@@ -1,12 +1,36 @@
 error: casting a character literal to `u8` truncates
-  --> tests/ui/char_lit_as_u8.rs:5:13
+  --> tests/ui/char_lit_as_u8.rs:4:13
    |
-LL |     let _ = '❤' as u8;
-   |             ^^^^^^^^^
+LL |     let _ = 'a' as u8;
+   |             ^^^^^^^^^ help: use a byte literal instead: `b'a'`
    |
    = note: `char` is four bytes wide, but `u8` is a single byte
    = note: `-D clippy::char-lit-as-u8` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::char_lit_as_u8)]`
 
-error: aborting due to 1 previous error
+error: casting a character literal to `u8` truncates
+  --> tests/ui/char_lit_as_u8.rs:6:13
+   |
+LL |     let _ = '\n' as u8;
+   |             ^^^^^^^^^^ help: use a byte literal instead: `b'\n'`
+   |
+   = note: `char` is four bytes wide, but `u8` is a single byte
+
+error: casting a character literal to `u8` truncates
+  --> tests/ui/char_lit_as_u8.rs:8:13
+   |
+LL |     let _ = '\0' as u8;
+   |             ^^^^^^^^^^ help: use a byte literal instead: `b'\0'`
+   |
+   = note: `char` is four bytes wide, but `u8` is a single byte
+
+error: casting a character literal to `u8` truncates
+  --> tests/ui/char_lit_as_u8.rs:10:13
+   |
+LL |     let _ = '\x01' as u8;
+   |             ^^^^^^^^^^^^ help: use a byte literal instead: `b'\x01'`
+   |
+   = note: `char` is four bytes wide, but `u8` is a single byte
+
+error: aborting due to 4 previous errors
 
diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs
deleted file mode 100644
index a8f39e27605..00000000000
--- a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-#![warn(clippy::char_lit_as_u8)]
-
-fn main() {
-    let _ = 'a' as u8;
-    //~^ char_lit_as_u8
-    let _ = '\n' as u8;
-    //~^ char_lit_as_u8
-    let _ = '\0' as u8;
-    //~^ char_lit_as_u8
-    let _ = '\x01' as u8;
-    //~^ char_lit_as_u8
-}
diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr
deleted file mode 100644
index 158dfd6bed2..00000000000
--- a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr
+++ /dev/null
@@ -1,36 +0,0 @@
-error: casting a character literal to `u8` truncates
-  --> tests/ui/char_lit_as_u8_suggestions.rs:4:13
-   |
-LL |     let _ = 'a' as u8;
-   |             ^^^^^^^^^ help: use a byte literal instead: `b'a'`
-   |
-   = note: `char` is four bytes wide, but `u8` is a single byte
-   = note: `-D clippy::char-lit-as-u8` implied by `-D warnings`
-   = help: to override `-D warnings` add `#[allow(clippy::char_lit_as_u8)]`
-
-error: casting a character literal to `u8` truncates
-  --> tests/ui/char_lit_as_u8_suggestions.rs:6:13
-   |
-LL |     let _ = '\n' as u8;
-   |             ^^^^^^^^^^ help: use a byte literal instead: `b'\n'`
-   |
-   = note: `char` is four bytes wide, but `u8` is a single byte
-
-error: casting a character literal to `u8` truncates
-  --> tests/ui/char_lit_as_u8_suggestions.rs:8:13
-   |
-LL |     let _ = '\0' as u8;
-   |             ^^^^^^^^^^ help: use a byte literal instead: `b'\0'`
-   |
-   = note: `char` is four bytes wide, but `u8` is a single byte
-
-error: casting a character literal to `u8` truncates
-  --> tests/ui/char_lit_as_u8_suggestions.rs:10:13
-   |
-LL |     let _ = '\x01' as u8;
-   |             ^^^^^^^^^^^^ help: use a byte literal instead: `b'\x01'`
-   |
-   = note: `char` is four bytes wide, but `u8` is a single byte
-
-error: aborting due to 4 previous errors
-
diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_unfixable.rs b/src/tools/clippy/tests/ui/char_lit_as_u8_unfixable.rs
new file mode 100644
index 00000000000..e5c094f158e
--- /dev/null
+++ b/src/tools/clippy/tests/ui/char_lit_as_u8_unfixable.rs
@@ -0,0 +1,8 @@
+//@no-rustfix
+#![warn(clippy::char_lit_as_u8)]
+
+fn main() {
+    // no suggestion, since a byte literal won't work.
+    let _ = '❤' as u8;
+    //~^ char_lit_as_u8
+}
diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_unfixable.stderr b/src/tools/clippy/tests/ui/char_lit_as_u8_unfixable.stderr
new file mode 100644
index 00000000000..49e555ae638
--- /dev/null
+++ b/src/tools/clippy/tests/ui/char_lit_as_u8_unfixable.stderr
@@ -0,0 +1,12 @@
+error: casting a character literal to `u8` truncates
+  --> tests/ui/char_lit_as_u8_unfixable.rs:6:13
+   |
+LL |     let _ = '❤' as u8;
+   |             ^^^^^^^^^
+   |
+   = note: `char` is four bytes wide, but `u8` is a single byte
+   = note: `-D clippy::char-lit-as-u8` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::char_lit_as_u8)]`
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr
index 26e360112b6..939b509d85c 100644
--- a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr
+++ b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr
@@ -330,7 +330,7 @@ LL |         if X.is_some() {
    |
    = note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html>
    = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
-   = note: `#[deny(static_mut_refs)]` on by default
+   = note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default
 
 error: aborting due to 36 previous errors
 
diff --git a/src/tools/clippy/tests/ui/deref_addrof.fixed b/src/tools/clippy/tests/ui/deref_addrof.fixed
index 35dbd790e89..ffe7f7d1440 100644
--- a/src/tools/clippy/tests/ui/deref_addrof.fixed
+++ b/src/tools/clippy/tests/ui/deref_addrof.fixed
@@ -1,11 +1,11 @@
-//@aux-build:proc_macros.rs
-
-#![allow(clippy::return_self_not_must_use, clippy::useless_vec)]
+#![allow(
+    dangerous_implicit_autorefs,
+    clippy::explicit_auto_deref,
+    clippy::return_self_not_must_use,
+    clippy::useless_vec
+)]
 #![warn(clippy::deref_addrof)]
 
-extern crate proc_macros;
-use proc_macros::inline_macros;
-
 fn get_number() -> usize {
     10
 }
@@ -56,19 +56,75 @@ fn main() {
     //~^ deref_addrof
     // do NOT lint for array as semantic differences with/out `*&`.
     let _arr = *&[0, 1, 2, 3, 4];
+
+    // Do not lint when text comes from macro
+    macro_rules! mac {
+        (dr) => {
+            *&0
+        };
+        (dr $e:expr) => {
+            *&$e
+        };
+        (r $e:expr) => {
+            &$e
+        };
+    }
+    let b = mac!(dr);
+    let b = mac!(dr a);
+    let b = *mac!(r a);
 }
 
-#[derive(Copy, Clone)]
-pub struct S;
-#[inline_macros]
-impl S {
-    pub fn f(&self) -> &Self {
-        inline!($(@expr self))
-        //~^ deref_addrof
+fn issue14386() {
+    use std::mem::ManuallyDrop;
+
+    #[derive(Copy, Clone)]
+    struct Data {
+        num: u64,
     }
-    #[allow(unused_mut)] // mut will be unused, once the macro is fixed
-    pub fn f_mut(mut self) -> Self {
-        inline!($(@expr self))
+
+    #[derive(Clone, Copy)]
+    struct M {
+        md: ManuallyDrop<[u8; 4]>,
+    }
+
+    union DataWithPadding<'lt> {
+        data: ManuallyDrop<Data>,
+        prim: ManuallyDrop<u64>,
+        padding: [u8; size_of::<Data>()],
+        tup: (ManuallyDrop<Data>, ()),
+        indirect: M,
+        indirect_arr: [M; 2],
+        indirect_ref: &'lt mut M,
+    }
+
+    let mut a = DataWithPadding {
+        padding: [0; size_of::<DataWithPadding>()],
+    };
+    unsafe {
+        a.padding = [1; size_of::<DataWithPadding>()];
         //~^ deref_addrof
+        a.tup.1 = ();
+        //~^ deref_addrof
+        *a.prim = 0;
+        //~^ deref_addrof
+
+        (*a.data).num = 42;
+        //~^ deref_addrof
+        (*a.indirect.md)[3] = 1;
+        //~^ deref_addrof
+        (*a.indirect_arr[1].md)[3] = 1;
+        //~^ deref_addrof
+        (*a.indirect_ref.md)[3] = 1;
+        //~^ deref_addrof
+
+        // Check that raw pointers are properly considered as well
+        *a.prim = 0;
+        //~^ deref_addrof
+        (*a.data).num = 42;
+        //~^ deref_addrof
+
+        // Do not lint, as the dereference happens later, we cannot
+        // just remove `&mut`
+        (*&mut a.tup).0.num = 42;
     }
 }
diff --git a/src/tools/clippy/tests/ui/deref_addrof.rs b/src/tools/clippy/tests/ui/deref_addrof.rs
index 96d1b92ef7b..bc253716aff 100644
--- a/src/tools/clippy/tests/ui/deref_addrof.rs
+++ b/src/tools/clippy/tests/ui/deref_addrof.rs
@@ -1,11 +1,11 @@
-//@aux-build:proc_macros.rs
-
-#![allow(clippy::return_self_not_must_use, clippy::useless_vec)]
+#![allow(
+    dangerous_implicit_autorefs,
+    clippy::explicit_auto_deref,
+    clippy::return_self_not_must_use,
+    clippy::useless_vec
+)]
 #![warn(clippy::deref_addrof)]
 
-extern crate proc_macros;
-use proc_macros::inline_macros;
-
 fn get_number() -> usize {
     10
 }
@@ -56,19 +56,75 @@ fn main() {
     //~^ deref_addrof
     // do NOT lint for array as semantic differences with/out `*&`.
     let _arr = *&[0, 1, 2, 3, 4];
+
+    // Do not lint when text comes from macro
+    macro_rules! mac {
+        (dr) => {
+            *&0
+        };
+        (dr $e:expr) => {
+            *&$e
+        };
+        (r $e:expr) => {
+            &$e
+        };
+    }
+    let b = mac!(dr);
+    let b = mac!(dr a);
+    let b = *mac!(r a);
 }
 
-#[derive(Copy, Clone)]
-pub struct S;
-#[inline_macros]
-impl S {
-    pub fn f(&self) -> &Self {
-        inline!(*& $(@expr self))
-        //~^ deref_addrof
+fn issue14386() {
+    use std::mem::ManuallyDrop;
+
+    #[derive(Copy, Clone)]
+    struct Data {
+        num: u64,
     }
-    #[allow(unused_mut)] // mut will be unused, once the macro is fixed
-    pub fn f_mut(mut self) -> Self {
-        inline!(*&mut $(@expr self))
+
+    #[derive(Clone, Copy)]
+    struct M {
+        md: ManuallyDrop<[u8; 4]>,
+    }
+
+    union DataWithPadding<'lt> {
+        data: ManuallyDrop<Data>,
+        prim: ManuallyDrop<u64>,
+        padding: [u8; size_of::<Data>()],
+        tup: (ManuallyDrop<Data>, ()),
+        indirect: M,
+        indirect_arr: [M; 2],
+        indirect_ref: &'lt mut M,
+    }
+
+    let mut a = DataWithPadding {
+        padding: [0; size_of::<DataWithPadding>()],
+    };
+    unsafe {
+        (*&mut a.padding) = [1; size_of::<DataWithPadding>()];
         //~^ deref_addrof
+        (*&mut a.tup).1 = ();
+        //~^ deref_addrof
+        **&mut a.prim = 0;
+        //~^ deref_addrof
+
+        (*&mut a.data).num = 42;
+        //~^ deref_addrof
+        (*&mut a.indirect.md)[3] = 1;
+        //~^ deref_addrof
+        (*&mut a.indirect_arr[1].md)[3] = 1;
+        //~^ deref_addrof
+        (*&mut a.indirect_ref.md)[3] = 1;
+        //~^ deref_addrof
+
+        // Check that raw pointers are properly considered as well
+        **&raw mut a.prim = 0;
+        //~^ deref_addrof
+        (*&raw mut a.data).num = 42;
+        //~^ deref_addrof
+
+        // Do not lint, as the dereference happens later, we cannot
+        // just remove `&mut`
+        (*&mut a.tup).0.num = 42;
     }
 }
diff --git a/src/tools/clippy/tests/ui/deref_addrof.stderr b/src/tools/clippy/tests/ui/deref_addrof.stderr
index 81414b625b2..65dd904a8f7 100644
--- a/src/tools/clippy/tests/ui/deref_addrof.stderr
+++ b/src/tools/clippy/tests/ui/deref_addrof.stderr
@@ -56,20 +56,58 @@ LL |     let _repeat = *&[0; 64];
    |                   ^^^^^^^^^ help: try: `[0; 64]`
 
 error: immediately dereferencing a reference
-  --> tests/ui/deref_addrof.rs:66:17
+  --> tests/ui/deref_addrof.rs:104:9
    |
-LL |         inline!(*& $(@expr self))
-   |                 ^^^^^^^^^^^^^^^^ help: try: `$(@expr self)`
+LL |         (*&mut a.padding) = [1; size_of::<DataWithPadding>()];
+   |         ^^^^^^^^^^^^^^^^^ help: try: `a.padding`
+
+error: immediately dereferencing a reference
+  --> tests/ui/deref_addrof.rs:106:9
+   |
+LL |         (*&mut a.tup).1 = ();
+   |         ^^^^^^^^^^^^^ help: try: `a.tup`
+
+error: immediately dereferencing a reference
+  --> tests/ui/deref_addrof.rs:108:10
    |
-   = note: this error originates in the macro `__inline_mac_impl` (in Nightly builds, run with -Z macro-backtrace for more info)
+LL |         **&mut a.prim = 0;
+   |          ^^^^^^^^^^^^ help: try: `a.prim`
 
 error: immediately dereferencing a reference
-  --> tests/ui/deref_addrof.rs:71:17
+  --> tests/ui/deref_addrof.rs:111:9
    |
-LL |         inline!(*&mut $(@expr self))
-   |                 ^^^^^^^^^^^^^^^^^^^ help: try: `$(@expr self)`
+LL |         (*&mut a.data).num = 42;
+   |         ^^^^^^^^^^^^^^ help: try: `(*a.data)`
+
+error: immediately dereferencing a reference
+  --> tests/ui/deref_addrof.rs:113:9
+   |
+LL |         (*&mut a.indirect.md)[3] = 1;
+   |         ^^^^^^^^^^^^^^^^^^^^^ help: try: `(*a.indirect.md)`
+
+error: immediately dereferencing a reference
+  --> tests/ui/deref_addrof.rs:115:9
+   |
+LL |         (*&mut a.indirect_arr[1].md)[3] = 1;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*a.indirect_arr[1].md)`
+
+error: immediately dereferencing a reference
+  --> tests/ui/deref_addrof.rs:117:9
+   |
+LL |         (*&mut a.indirect_ref.md)[3] = 1;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*a.indirect_ref.md)`
+
+error: immediately dereferencing a reference
+  --> tests/ui/deref_addrof.rs:121:10
+   |
+LL |         **&raw mut a.prim = 0;
+   |          ^^^^^^^^^^^^^^^^ help: try: `a.prim`
+
+error: immediately dereferencing a reference
+  --> tests/ui/deref_addrof.rs:123:9
    |
-   = note: this error originates in the macro `__inline_mac_impl` (in Nightly builds, run with -Z macro-backtrace for more info)
+LL |         (*&raw mut a.data).num = 42;
+   |         ^^^^^^^^^^^^^^^^^^ help: try: `(*a.data)`
 
-error: aborting due to 11 previous errors
+error: aborting due to 18 previous errors
 
diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.fixed b/src/tools/clippy/tests/ui/doc/doc-fixable.fixed
index bbbd5973036..423a73734da 100644
--- a/src/tools/clippy/tests/ui/doc/doc-fixable.fixed
+++ b/src/tools/clippy/tests/ui/doc/doc-fixable.fixed
@@ -74,7 +74,7 @@ fn test_units() {
 /// GitHub GitLab
 /// IPv4 IPv6
 /// ClojureScript CoffeeScript JavaScript PostScript PureScript TypeScript
-/// WebAssembly
+/// PowerPC WebAssembly
 /// NaN NaNs
 /// OAuth GraphQL
 /// OCaml
diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.rs b/src/tools/clippy/tests/ui/doc/doc-fixable.rs
index 1077d3580d3..8deffb4210e 100644
--- a/src/tools/clippy/tests/ui/doc/doc-fixable.rs
+++ b/src/tools/clippy/tests/ui/doc/doc-fixable.rs
@@ -74,7 +74,7 @@ fn test_units() {
 /// GitHub GitLab
 /// IPv4 IPv6
 /// ClojureScript CoffeeScript JavaScript PostScript PureScript TypeScript
-/// WebAssembly
+/// PowerPC WebAssembly
 /// NaN NaNs
 /// OAuth GraphQL
 /// OCaml
diff --git a/src/tools/clippy/tests/ui/double_ended_iterator_last.fixed b/src/tools/clippy/tests/ui/double_ended_iterator_last.fixed
index be31ee5fb48..180a513d0f8 100644
--- a/src/tools/clippy/tests/ui/double_ended_iterator_last.fixed
+++ b/src/tools/clippy/tests/ui/double_ended_iterator_last.fixed
@@ -81,6 +81,11 @@ fn issue_14139() {
     let (subindex, _) = (index.by_ref().take(3), 42);
     let _ = subindex.last();
     let _ = index.next();
+
+    let mut index = [true, true, false, false, false, true].iter();
+    let subindex = (index.by_ref().take(3), 42);
+    let _ = subindex.0.last();
+    let _ = index.next();
 }
 
 fn drop_order() {
@@ -108,6 +113,12 @@ fn drop_order() {
     let mut v = DropDeIterator(v.into_iter());
     println!("Last element is {}", v.next_back().unwrap().0);
     //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
+
+    let v = vec![S("four"), S("five"), S("six")];
+    let mut v = (DropDeIterator(v.into_iter()), 42);
+    println!("Last element is {}", v.0.next_back().unwrap().0);
+    //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
+
     println!("Done");
 }
 
diff --git a/src/tools/clippy/tests/ui/double_ended_iterator_last.rs b/src/tools/clippy/tests/ui/double_ended_iterator_last.rs
index 30864e15bce..3dd72cfeaac 100644
--- a/src/tools/clippy/tests/ui/double_ended_iterator_last.rs
+++ b/src/tools/clippy/tests/ui/double_ended_iterator_last.rs
@@ -81,6 +81,11 @@ fn issue_14139() {
     let (subindex, _) = (index.by_ref().take(3), 42);
     let _ = subindex.last();
     let _ = index.next();
+
+    let mut index = [true, true, false, false, false, true].iter();
+    let subindex = (index.by_ref().take(3), 42);
+    let _ = subindex.0.last();
+    let _ = index.next();
 }
 
 fn drop_order() {
@@ -108,6 +113,12 @@ fn drop_order() {
     let v = DropDeIterator(v.into_iter());
     println!("Last element is {}", v.last().unwrap().0);
     //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
+
+    let v = vec![S("four"), S("five"), S("six")];
+    let v = (DropDeIterator(v.into_iter()), 42);
+    println!("Last element is {}", v.0.last().unwrap().0);
+    //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
+
     println!("Done");
 }
 
diff --git a/src/tools/clippy/tests/ui/double_ended_iterator_last.stderr b/src/tools/clippy/tests/ui/double_ended_iterator_last.stderr
index 72a6ead47a9..0f0056be376 100644
--- a/src/tools/clippy/tests/ui/double_ended_iterator_last.stderr
+++ b/src/tools/clippy/tests/ui/double_ended_iterator_last.stderr
@@ -18,7 +18,7 @@ LL |     let _ = DeIterator.last();
    |                        help: try: `next_back()`
 
 error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
-  --> tests/ui/double_ended_iterator_last.rs:109:36
+  --> tests/ui/double_ended_iterator_last.rs:114:36
    |
 LL |     println!("Last element is {}", v.last().unwrap().0);
    |                                    ^^^^^^^^
@@ -30,5 +30,18 @@ LL ~     let mut v = DropDeIterator(v.into_iter());
 LL ~     println!("Last element is {}", v.next_back().unwrap().0);
    |
 
-error: aborting due to 3 previous errors
+error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
+  --> tests/ui/double_ended_iterator_last.rs:119:36
+   |
+LL |     println!("Last element is {}", v.0.last().unwrap().0);
+   |                                    ^^^^^^^^^^
+   |
+   = note: this change will alter drop order which may be undesirable
+help: try
+   |
+LL ~     let mut v = (DropDeIterator(v.into_iter()), 42);
+LL ~     println!("Last element is {}", v.0.next_back().unwrap().0);
+   |
+
+error: aborting due to 4 previous errors
 
diff --git a/src/tools/clippy/tests/ui/double_ended_iterator_last_unfixable.rs b/src/tools/clippy/tests/ui/double_ended_iterator_last_unfixable.rs
deleted file mode 100644
index 73f62ac1246..00000000000
--- a/src/tools/clippy/tests/ui/double_ended_iterator_last_unfixable.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-//@no-rustfix: requires manual changes
-#![warn(clippy::double_ended_iterator_last)]
-
-// Should not be linted because applying the lint would move the original iterator. This can only be
-// linted if the iterator is used thereafter.
-fn main() {
-    let mut index = [true, true, false, false, false, true].iter();
-    let subindex = (index.by_ref().take(3), 42);
-    let _ = subindex.0.last();
-    let _ = index.next();
-}
-
-fn drop_order() {
-    struct DropDeIterator(std::vec::IntoIter<S>);
-    impl Iterator for DropDeIterator {
-        type Item = S;
-        fn next(&mut self) -> Option<Self::Item> {
-            self.0.next()
-        }
-    }
-    impl DoubleEndedIterator for DropDeIterator {
-        fn next_back(&mut self) -> Option<Self::Item> {
-            self.0.next_back()
-        }
-    }
-
-    struct S(&'static str);
-    impl std::ops::Drop for S {
-        fn drop(&mut self) {
-            println!("Dropping {}", self.0);
-        }
-    }
-
-    let v = vec![S("one"), S("two"), S("three")];
-    let v = (DropDeIterator(v.into_iter()), 42);
-    println!("Last element is {}", v.0.last().unwrap().0);
-    //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
-    println!("Done");
-}
diff --git a/src/tools/clippy/tests/ui/double_ended_iterator_last_unfixable.stderr b/src/tools/clippy/tests/ui/double_ended_iterator_last_unfixable.stderr
deleted file mode 100644
index e330a22a354..00000000000
--- a/src/tools/clippy/tests/ui/double_ended_iterator_last_unfixable.stderr
+++ /dev/null
@@ -1,19 +0,0 @@
-error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
-  --> tests/ui/double_ended_iterator_last_unfixable.rs:36:36
-   |
-LL |     println!("Last element is {}", v.0.last().unwrap().0);
-   |                                    ^^^^------
-   |                                        |
-   |                                        help: try: `next_back()`
-   |
-   = note: this change will alter drop order which may be undesirable
-note: this must be made mutable to use `.next_back()`
-  --> tests/ui/double_ended_iterator_last_unfixable.rs:36:36
-   |
-LL |     println!("Last element is {}", v.0.last().unwrap().0);
-   |                                    ^^^
-   = note: `-D clippy::double-ended-iterator-last` implied by `-D warnings`
-   = help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]`
-
-error: aborting due to 1 previous error
-
diff --git a/src/tools/clippy/tests/ui/eta.fixed b/src/tools/clippy/tests/ui/eta.fixed
index c93b83f53ec..3d2b41b8fb8 100644
--- a/src/tools/clippy/tests/ui/eta.fixed
+++ b/src/tools/clippy/tests/ui/eta.fixed
@@ -1,3 +1,6 @@
+// we have some HELP annotations -- don't complain about them not being present everywhere
+//@require-annotations-for-level: ERROR
+
 #![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)]
 #![allow(unused)]
 #![allow(
@@ -561,3 +564,29 @@ fn issue_14789() {
         std::convert::identity,
     );
 }
+
+fn issue8817() {
+    fn f(_: u32) -> u32 {
+        todo!()
+    }
+    let g = |_: u32| -> u32 { todo!() };
+    struct S(u32);
+    enum MyError {
+        A(S),
+    }
+
+    Some(5)
+        .map(f)
+        //~^ redundant_closure
+        //~| HELP: replace the closure with the function itself
+        .map(g)
+        //~^ redundant_closure
+        //~| HELP: replace the closure with the function itself
+        .map(S)
+        //~^ redundant_closure
+        //~| HELP: replace the closure with the tuple struct itself
+        .map(MyError::A)
+        //~^ redundant_closure
+        //~| HELP: replace the closure with the tuple variant itself
+        .unwrap(); // just for nicer formatting
+}
diff --git a/src/tools/clippy/tests/ui/eta.rs b/src/tools/clippy/tests/ui/eta.rs
index 273c8b21f4a..79d1103410d 100644
--- a/src/tools/clippy/tests/ui/eta.rs
+++ b/src/tools/clippy/tests/ui/eta.rs
@@ -1,3 +1,6 @@
+// we have some HELP annotations -- don't complain about them not being present everywhere
+//@require-annotations-for-level: ERROR
+
 #![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)]
 #![allow(unused)]
 #![allow(
@@ -561,3 +564,29 @@ fn issue_14789() {
         std::convert::identity,
     );
 }
+
+fn issue8817() {
+    fn f(_: u32) -> u32 {
+        todo!()
+    }
+    let g = |_: u32| -> u32 { todo!() };
+    struct S(u32);
+    enum MyError {
+        A(S),
+    }
+
+    Some(5)
+        .map(|n| f(n))
+        //~^ redundant_closure
+        //~| HELP: replace the closure with the function itself
+        .map(|n| g(n))
+        //~^ redundant_closure
+        //~| HELP: replace the closure with the function itself
+        .map(|n| S(n))
+        //~^ redundant_closure
+        //~| HELP: replace the closure with the tuple struct itself
+        .map(|n| MyError::A(n))
+        //~^ redundant_closure
+        //~| HELP: replace the closure with the tuple variant itself
+        .unwrap(); // just for nicer formatting
+}
diff --git a/src/tools/clippy/tests/ui/eta.stderr b/src/tools/clippy/tests/ui/eta.stderr
index 8bc08add2fa..aa32ed1a38e 100644
--- a/src/tools/clippy/tests/ui/eta.stderr
+++ b/src/tools/clippy/tests/ui/eta.stderr
@@ -1,5 +1,5 @@
 error: redundant closure
-  --> tests/ui/eta.rs:31:27
+  --> tests/ui/eta.rs:34:27
    |
 LL |     let a = Some(1u8).map(|a| foo(a));
    |                           ^^^^^^^^^^ help: replace the closure with the function itself: `foo`
@@ -8,31 +8,31 @@ LL |     let a = Some(1u8).map(|a| foo(a));
    = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]`
 
 error: redundant closure
-  --> tests/ui/eta.rs:36:40
+  --> tests/ui/eta.rs:39:40
    |
 LL |     let _: Option<Vec<u8>> = true.then(|| vec![]); // special case vec!
    |                                        ^^^^^^^^^ help: replace the closure with `Vec::new`: `std::vec::Vec::new`
 
 error: redundant closure
-  --> tests/ui/eta.rs:39:35
+  --> tests/ui/eta.rs:42:35
    |
 LL |     let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted?
    |                                   ^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo2`
 
 error: redundant closure
-  --> tests/ui/eta.rs:42:26
+  --> tests/ui/eta.rs:45:26
    |
 LL |     all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted
    |                          ^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `below`
 
 error: redundant closure
-  --> tests/ui/eta.rs:51:27
+  --> tests/ui/eta.rs:54:27
    |
 LL |     let e = Some(1u8).map(|a| generic(a));
    |                           ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `generic`
 
 error: redundant closure
-  --> tests/ui/eta.rs:104:51
+  --> tests/ui/eta.rs:107:51
    |
 LL |     let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo());
    |                                                   ^^^^^^^^^^^ help: replace the closure with the method itself: `TestStruct::foo`
@@ -41,178 +41,202 @@ LL |     let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo());
    = help: to override `-D warnings` add `#[allow(clippy::redundant_closure_for_method_calls)]`
 
 error: redundant closure
-  --> tests/ui/eta.rs:106:51
+  --> tests/ui/eta.rs:109:51
    |
 LL |     let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo());
    |                                                   ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `TestTrait::trait_foo`
 
 error: redundant closure
-  --> tests/ui/eta.rs:109:42
+  --> tests/ui/eta.rs:112:42
    |
 LL |     let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear());
    |                                          ^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::vec::Vec::clear`
 
 error: redundant closure
-  --> tests/ui/eta.rs:114:29
+  --> tests/ui/eta.rs:117:29
    |
 LL |     let e = Some("str").map(|s| s.to_string());
    |                             ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::string::ToString::to_string`
 
 error: redundant closure
-  --> tests/ui/eta.rs:116:27
+  --> tests/ui/eta.rs:119:27
    |
 LL |     let e = Some('a').map(|s| s.to_uppercase());
    |                           ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_uppercase`
 
 error: redundant closure
-  --> tests/ui/eta.rs:119:65
+  --> tests/ui/eta.rs:122:65
    |
 LL |     let e: std::vec::Vec<char> = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect();
    |                                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_ascii_uppercase`
 
 error: redundant closure
-  --> tests/ui/eta.rs:136:23
+  --> tests/ui/eta.rs:139:23
    |
 LL |         let _ = x.map(|x| x.parse::<i16>());
    |                       ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `str::parse::<i16>`
 
 error: redundant closure
-  --> tests/ui/eta.rs:189:22
+  --> tests/ui/eta.rs:192:22
    |
 LL |     requires_fn_once(|| x());
    |                      ^^^^^^ help: replace the closure with the function itself: `x`
 
 error: redundant closure
-  --> tests/ui/eta.rs:197:27
+  --> tests/ui/eta.rs:200:27
    |
 LL |     let a = Some(1u8).map(|a| foo_ptr(a));
    |                           ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo_ptr`
 
 error: redundant closure
-  --> tests/ui/eta.rs:203:27
+  --> tests/ui/eta.rs:206:27
    |
 LL |     let a = Some(1u8).map(|a| closure(a));
    |                           ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `closure`
 
 error: redundant closure
-  --> tests/ui/eta.rs:236:28
+  --> tests/ui/eta.rs:239:28
    |
 LL |     x.into_iter().for_each(|x| add_to_res(x));
    |                            ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res`
 
 error: redundant closure
-  --> tests/ui/eta.rs:238:28
+  --> tests/ui/eta.rs:241:28
    |
 LL |     y.into_iter().for_each(|x| add_to_res(x));
    |                            ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res`
 
 error: redundant closure
-  --> tests/ui/eta.rs:240:28
+  --> tests/ui/eta.rs:243:28
    |
 LL |     z.into_iter().for_each(|x| add_to_res(x));
    |                            ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `add_to_res`
 
 error: redundant closure
-  --> tests/ui/eta.rs:248:21
+  --> tests/ui/eta.rs:251:21
    |
 LL |         Some(1).map(|n| closure(n));
    |                     ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut closure`
 
 error: redundant closure
-  --> tests/ui/eta.rs:253:21
+  --> tests/ui/eta.rs:256:21
    |
 LL |         Some(1).map(|n| in_loop(n));
    |                     ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `in_loop`
 
 error: redundant closure
-  --> tests/ui/eta.rs:347:18
+  --> tests/ui/eta.rs:350:18
    |
 LL |     takes_fn_mut(|| f());
    |                  ^^^^^^ help: replace the closure with the function itself: `&mut f`
 
 error: redundant closure
-  --> tests/ui/eta.rs:351:19
+  --> tests/ui/eta.rs:354:19
    |
 LL |     takes_fn_once(|| f());
    |                   ^^^^^^ help: replace the closure with the function itself: `&mut f`
 
 error: redundant closure
-  --> tests/ui/eta.rs:356:26
+  --> tests/ui/eta.rs:359:26
    |
 LL |     move || takes_fn_mut(|| f_used_once())
    |                          ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut f_used_once`
 
 error: redundant closure
-  --> tests/ui/eta.rs:369:19
+  --> tests/ui/eta.rs:372:19
    |
 LL |     array_opt.map(|a| a.as_slice());
    |                   ^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<[u8; 3]>::as_slice`
 
 error: redundant closure
-  --> tests/ui/eta.rs:373:19
+  --> tests/ui/eta.rs:376:19
    |
 LL |     slice_opt.map(|s| s.len());
    |                   ^^^^^^^^^^^ help: replace the closure with the method itself: `<[u8]>::len`
 
 error: redundant closure
-  --> tests/ui/eta.rs:377:17
+  --> tests/ui/eta.rs:380:17
    |
 LL |     ptr_opt.map(|p| p.is_null());
    |                 ^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<*const usize>::is_null`
 
 error: redundant closure
-  --> tests/ui/eta.rs:382:17
+  --> tests/ui/eta.rs:385:17
    |
 LL |     dyn_opt.map(|d| d.method_on_dyn());
    |                 ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<dyn TestTrait>::method_on_dyn`
 
 error: redundant closure
-  --> tests/ui/eta.rs:443:19
+  --> tests/ui/eta.rs:446:19
    |
 LL |     let _ = f(&0, |x, y| f2(x, y));
    |                   ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2`
 
 error: redundant closure
-  --> tests/ui/eta.rs:472:22
+  --> tests/ui/eta.rs:475:22
    |
 LL |             test.map(|t| t.method())
    |                      ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `Test::method`
 
 error: redundant closure
-  --> tests/ui/eta.rs:477:22
+  --> tests/ui/eta.rs:480:22
    |
 LL |             test.map(|t| t.method())
    |                      ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `super::Outer::method`
 
 error: redundant closure
-  --> tests/ui/eta.rs:491:18
+  --> tests/ui/eta.rs:494:18
    |
 LL |         test.map(|t| t.method())
    |                  ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `test_mod::Test::method`
 
 error: redundant closure
-  --> tests/ui/eta.rs:499:30
+  --> tests/ui/eta.rs:502:30
    |
 LL |                     test.map(|t| t.method())
    |                              ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `crate::issue_10854::d::Test::method`
 
 error: redundant closure
-  --> tests/ui/eta.rs:519:38
+  --> tests/ui/eta.rs:522:38
    |
 LL |         let x = Box::new(|| None.map(|x| f(x)));
    |                                      ^^^^^^^^ help: replace the closure with the function itself: `&f`
 
 error: redundant closure
-  --> tests/ui/eta.rs:524:38
+  --> tests/ui/eta.rs:527:38
    |
 LL |         let x = Box::new(|| None.map(|x| f(x)));
    |                                      ^^^^^^^^ help: replace the closure with the function itself: `f`
 
 error: redundant closure
-  --> tests/ui/eta.rs:542:35
+  --> tests/ui/eta.rs:545:35
    |
 LL |         let _field = bind.or_else(|| get_default()).unwrap();
    |                                   ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `get_default`
 
-error: aborting due to 35 previous errors
+error: redundant closure
+  --> tests/ui/eta.rs:588:14
+   |
+LL |         .map(|n| MyError::A(n))
+   |              ^^^^^^^^^^^^^^^^^ help: replace the closure with the tuple variant itself: `MyError::A`
+
+error: redundant closure
+  --> tests/ui/eta.rs:585:14
+   |
+LL |         .map(|n| S(n))
+   |              ^^^^^^^^ help: replace the closure with the tuple struct itself: `S`
+
+error: redundant closure
+  --> tests/ui/eta.rs:582:14
+   |
+LL |         .map(|n| g(n))
+   |              ^^^^^^^^ help: replace the closure with the function itself: `g`
+
+error: redundant closure
+  --> tests/ui/eta.rs:579:14
+   |
+LL |         .map(|n| f(n))
+   |              ^^^^^^^^ help: replace the closure with the function itself: `f`
+
+error: aborting due to 39 previous errors
 
diff --git a/src/tools/clippy/tests/ui/from_str_radix_10.fixed b/src/tools/clippy/tests/ui/from_str_radix_10.fixed
index 4b8fd778685..47d24167e56 100644
--- a/src/tools/clippy/tests/ui/from_str_radix_10.fixed
+++ b/src/tools/clippy/tests/ui/from_str_radix_10.fixed
@@ -74,3 +74,13 @@ fn issue_12731() {
         let _ = u32::from_str_radix("123", 10);
     }
 }
+
+fn fix_str_ref_check() {
+    #![allow(clippy::needless_borrow)]
+    let s = "1";
+    let _ = s.parse::<u32>().unwrap();
+    //~^ from_str_radix_10
+    let s_ref = &s;
+    let _ = s_ref.parse::<u32>().unwrap();
+    //~^ from_str_radix_10
+}
diff --git a/src/tools/clippy/tests/ui/from_str_radix_10.rs b/src/tools/clippy/tests/ui/from_str_radix_10.rs
index 89002b11a99..952e19b57a0 100644
--- a/src/tools/clippy/tests/ui/from_str_radix_10.rs
+++ b/src/tools/clippy/tests/ui/from_str_radix_10.rs
@@ -74,3 +74,13 @@ fn issue_12731() {
         let _ = u32::from_str_radix("123", 10);
     }
 }
+
+fn fix_str_ref_check() {
+    #![allow(clippy::needless_borrow)]
+    let s = "1";
+    let _ = u32::from_str_radix(&s, 10).unwrap();
+    //~^ from_str_radix_10
+    let s_ref = &s;
+    let _ = u32::from_str_radix(&s_ref, 10).unwrap();
+    //~^ from_str_radix_10
+}
diff --git a/src/tools/clippy/tests/ui/from_str_radix_10.stderr b/src/tools/clippy/tests/ui/from_str_radix_10.stderr
index c693e8f50ff..d4e6c3657f2 100644
--- a/src/tools/clippy/tests/ui/from_str_radix_10.stderr
+++ b/src/tools/clippy/tests/ui/from_str_radix_10.stderr
@@ -49,5 +49,17 @@ error: this call to `from_str_radix` can be replaced with a call to `str::parse`
 LL |     i32::from_str_radix(&stringier, 10)?;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `stringier.parse::<i32>()`
 
-error: aborting due to 8 previous errors
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> tests/ui/from_str_radix_10.rs:81:13
+   |
+LL |     let _ = u32::from_str_radix(&s, 10).unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `s.parse::<u32>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> tests/ui/from_str_radix_10.rs:84:13
+   |
+LL |     let _ = u32::from_str_radix(&s_ref, 10).unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `s_ref.parse::<u32>()`
+
+error: aborting due to 10 previous errors
 
diff --git a/src/tools/clippy/tests/ui/functions_maxlines.rs b/src/tools/clippy/tests/ui/functions_maxlines.rs
index e0990dadaaa..b0714552461 100644
--- a/src/tools/clippy/tests/ui/functions_maxlines.rs
+++ b/src/tools/clippy/tests/ui/functions_maxlines.rs
@@ -1,3 +1,4 @@
+#![allow(clippy::unused_unit, clippy::missing_safety_doc)]
 #![warn(clippy::too_many_lines)]
 
 fn good_lines() {
@@ -55,7 +56,8 @@ fn good_lines() {
     println!("This is good.");
 }
 
-fn bad_lines() {
+#[allow(unused)] // the attr shouldn't get included in the highlight
+pub async unsafe extern "Rust" fn bad_lines() -> () {
     //~^ too_many_lines
 
     println!("Dont get confused by braces: {{}}");
@@ -162,4 +164,115 @@ fn bad_lines() {
     println!("This is bad.");
 }
 
+struct Foo;
+impl Foo {
+    #[allow(unused)] // the attr shouldn't get included in the highlight
+    pub async unsafe extern "Rust" fn bad_lines() -> () {
+        //~^ too_many_lines
+
+        println!("Dont get confused by braces: {{}}");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+        println!("This is bad.");
+    }
+}
+
 fn main() {}
diff --git a/src/tools/clippy/tests/ui/functions_maxlines.stderr b/src/tools/clippy/tests/ui/functions_maxlines.stderr
index f42a2b2a22a..4c3faf45c47 100644
--- a/src/tools/clippy/tests/ui/functions_maxlines.stderr
+++ b/src/tools/clippy/tests/ui/functions_maxlines.stderr
@@ -1,17 +1,17 @@
-error: this function has too many lines (102/100)
-  --> tests/ui/functions_maxlines.rs:58:1
+error: this function has too many lines (104/100)
+  --> tests/ui/functions_maxlines.rs:60:1
    |
-LL | / fn bad_lines() {
-LL | |
-LL | |
-LL | |     println!("Dont get confused by braces: {{}}");
-...  |
-LL | |     println!("This is bad.");
-LL | | }
-   | |_^
+LL | pub async unsafe extern "Rust" fn bad_lines() -> () {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: `-D clippy::too-many-lines` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::too_many_lines)]`
 
-error: aborting due to 1 previous error
+error: this function has too many lines (104/100)
+  --> tests/ui/functions_maxlines.rs:170:5
+   |
+LL |     pub async unsafe extern "Rust" fn bad_lines() -> () {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
 
diff --git a/src/tools/clippy/tests/ui/infinite_loops.rs b/src/tools/clippy/tests/ui/infinite_loops.rs
index fcd1f795fff..9b8c3933197 100644
--- a/src/tools/clippy/tests/ui/infinite_loops.rs
+++ b/src/tools/clippy/tests/ui/infinite_loops.rs
@@ -450,4 +450,75 @@ mod issue_12338 {
     }
 }
 
+#[allow(clippy::let_underscore_future, clippy::empty_loop)]
+mod issue_14000 {
+    use super::do_something;
+
+    async fn foo() {
+        let _ = async move {
+            loop {
+                //~^ infinite_loop
+                do_something();
+            }
+        }
+        .await;
+        let _ = async move {
+            loop {
+                //~^ infinite_loop
+                continue;
+            }
+        }
+        .await;
+    }
+
+    fn bar() {
+        let _ = async move {
+            loop {
+                do_something();
+            }
+        };
+
+        let _ = async move {
+            loop {
+                continue;
+            }
+        };
+    }
+}
+
+#[allow(clippy::let_underscore_future)]
+mod tokio_spawn_test {
+    use super::do_something;
+
+    fn install_ticker() {
+        // This should NOT trigger the lint because the async block is spawned, not awaited
+        std::thread::spawn(move || {
+            async move {
+                loop {
+                    // This loop should not trigger infinite_loop lint
+                    do_something();
+                }
+            }
+        });
+    }
+
+    fn spawn_async_block() {
+        // This should NOT trigger the lint because the async block is not awaited
+        let _handle = async move {
+            loop {
+                do_something();
+            }
+        };
+    }
+
+    fn await_async_block() {
+        // This SHOULD trigger the lint because the async block is awaited
+        let _ = async move {
+            loop {
+                do_something();
+            }
+        };
+    }
+}
+
 fn main() {}
diff --git a/src/tools/clippy/tests/ui/infinite_loops.stderr b/src/tools/clippy/tests/ui/infinite_loops.stderr
index 4d02636ef4a..4c6b6f725f1 100644
--- a/src/tools/clippy/tests/ui/infinite_loops.stderr
+++ b/src/tools/clippy/tests/ui/infinite_loops.stderr
@@ -311,5 +311,27 @@ help: if this is intentional, consider specifying `!` as function return
 LL | fn continue_outer() -> ! {
    |                     ++++
 
-error: aborting due to 21 previous errors
+error: infinite loop detected
+  --> tests/ui/infinite_loops.rs:459:13
+   |
+LL | /             loop {
+LL | |
+LL | |                 do_something();
+LL | |             }
+   | |_____________^
+   |
+   = help: if this is not intended, try adding a `break` or `return` condition in the loop
+
+error: infinite loop detected
+  --> tests/ui/infinite_loops.rs:466:13
+   |
+LL | /             loop {
+LL | |
+LL | |                 continue;
+LL | |             }
+   | |_____________^
+   |
+   = help: if this is not intended, try adding a `break` or `return` condition in the loop
+
+error: aborting due to 23 previous errors
 
diff --git a/src/tools/clippy/tests/ui/match_bool.fixed b/src/tools/clippy/tests/ui/match_bool.fixed
index 1dfb82db120..876ae935afd 100644
--- a/src/tools/clippy/tests/ui/match_bool.fixed
+++ b/src/tools/clippy/tests/ui/match_bool.fixed
@@ -61,4 +61,17 @@ fn issue14099() {
     } }
 }
 
+fn issue15351() {
+    let mut d = false;
+    match d {
+        false => println!("foo"),
+        ref mut d => *d = false,
+    }
+
+    match d {
+        false => println!("foo"),
+        e => println!("{e}"),
+    }
+}
+
 fn main() {}
diff --git a/src/tools/clippy/tests/ui/match_bool.rs b/src/tools/clippy/tests/ui/match_bool.rs
index 719b4e51eb6..a134ad8346e 100644
--- a/src/tools/clippy/tests/ui/match_bool.rs
+++ b/src/tools/clippy/tests/ui/match_bool.rs
@@ -113,4 +113,17 @@ fn issue14099() {
     }
 }
 
+fn issue15351() {
+    let mut d = false;
+    match d {
+        false => println!("foo"),
+        ref mut d => *d = false,
+    }
+
+    match d {
+        false => println!("foo"),
+        e => println!("{e}"),
+    }
+}
+
 fn main() {}
diff --git a/src/tools/clippy/tests/ui/match_ref_pats.fixed b/src/tools/clippy/tests/ui/match_ref_pats.fixed
index 8add3da0c99..f727546838b 100644
--- a/src/tools/clippy/tests/ui/match_ref_pats.fixed
+++ b/src/tools/clippy/tests/ui/match_ref_pats.fixed
@@ -1,6 +1,12 @@
 #![warn(clippy::match_ref_pats)]
 #![allow(dead_code, unused_variables)]
-#![allow(clippy::enum_variant_names, clippy::equatable_if_let, clippy::uninlined_format_args)]
+#![allow(
+    clippy::enum_variant_names,
+    clippy::equatable_if_let,
+    clippy::uninlined_format_args,
+    clippy::empty_loop,
+    clippy::diverging_sub_expression
+)]
 
 fn ref_pats() {
     {
@@ -120,4 +126,32 @@ mod issue_7740 {
     }
 }
 
+mod issue15378 {
+    fn never_in_match() {
+        match unimplemented!() {
+            &_ => {},
+            &&&42 => {
+                todo!()
+            },
+            _ => {},
+        }
+
+        match panic!() {
+            &_ => {},
+            &&&42 => {
+                todo!()
+            },
+            _ => {},
+        }
+
+        match loop {} {
+            &_ => {},
+            &&&42 => {
+                todo!()
+            },
+            _ => {},
+        }
+    }
+}
+
 fn main() {}
diff --git a/src/tools/clippy/tests/ui/match_ref_pats.rs b/src/tools/clippy/tests/ui/match_ref_pats.rs
index 07889b0dfc2..eca4d584acd 100644
--- a/src/tools/clippy/tests/ui/match_ref_pats.rs
+++ b/src/tools/clippy/tests/ui/match_ref_pats.rs
@@ -1,6 +1,12 @@
 #![warn(clippy::match_ref_pats)]
 #![allow(dead_code, unused_variables)]
-#![allow(clippy::enum_variant_names, clippy::equatable_if_let, clippy::uninlined_format_args)]
+#![allow(
+    clippy::enum_variant_names,
+    clippy::equatable_if_let,
+    clippy::uninlined_format_args,
+    clippy::empty_loop,
+    clippy::diverging_sub_expression
+)]
 
 fn ref_pats() {
     {
@@ -120,4 +126,32 @@ mod issue_7740 {
     }
 }
 
+mod issue15378 {
+    fn never_in_match() {
+        match unimplemented!() {
+            &_ => {},
+            &&&42 => {
+                todo!()
+            },
+            _ => {},
+        }
+
+        match panic!() {
+            &_ => {},
+            &&&42 => {
+                todo!()
+            },
+            _ => {},
+        }
+
+        match loop {} {
+            &_ => {},
+            &&&42 => {
+                todo!()
+            },
+            _ => {},
+        }
+    }
+}
+
 fn main() {}
diff --git a/src/tools/clippy/tests/ui/match_ref_pats.stderr b/src/tools/clippy/tests/ui/match_ref_pats.stderr
index f81b290b32c..ecb08e6972d 100644
--- a/src/tools/clippy/tests/ui/match_ref_pats.stderr
+++ b/src/tools/clippy/tests/ui/match_ref_pats.stderr
@@ -1,5 +1,5 @@
 error: you don't need to add `&` to all patterns
-  --> tests/ui/match_ref_pats.rs:8:9
+  --> tests/ui/match_ref_pats.rs:14:9
    |
 LL | /         match v {
 LL | |
@@ -19,7 +19,7 @@ LL ~             None => println!("none"),
    |
 
 error: you don't need to add `&` to both the expression and the patterns
-  --> tests/ui/match_ref_pats.rs:26:5
+  --> tests/ui/match_ref_pats.rs:32:5
    |
 LL | /     match &w {
 LL | |
@@ -37,7 +37,7 @@ LL ~         None => println!("none"),
    |
 
 error: redundant pattern matching, consider using `is_none()`
-  --> tests/ui/match_ref_pats.rs:39:12
+  --> tests/ui/match_ref_pats.rs:45:12
    |
 LL |     if let &None = a {
    |     -------^^^^^---- help: try: `if a.is_none()`
@@ -46,13 +46,13 @@ LL |     if let &None = a {
    = help: to override `-D warnings` add `#[allow(clippy::redundant_pattern_matching)]`
 
 error: redundant pattern matching, consider using `is_none()`
-  --> tests/ui/match_ref_pats.rs:45:12
+  --> tests/ui/match_ref_pats.rs:51:12
    |
 LL |     if let &None = &b {
    |     -------^^^^^----- help: try: `if b.is_none()`
 
 error: you don't need to add `&` to all patterns
-  --> tests/ui/match_ref_pats.rs:106:9
+  --> tests/ui/match_ref_pats.rs:112:9
    |
 LL | /         match foobar_variant!(0) {
 LL | |
diff --git a/src/tools/clippy/tests/ui/ptr_arg.stderr b/src/tools/clippy/tests/ui/ptr_arg.stderr
index 87235057349..f32e83d8b81 100644
--- a/src/tools/clippy/tests/ui/ptr_arg.stderr
+++ b/src/tools/clippy/tests/ui/ptr_arg.stderr
@@ -268,10 +268,10 @@ LL |     fn barbar(_x: &mut Vec<u32>, y: &mut String) {
    |                                     ^^^^^^^^^^^ help: change this to: `&mut str`
 
 error: eliding a lifetime that's named elsewhere is confusing
-  --> tests/ui/ptr_arg.rs:314:36
+  --> tests/ui/ptr_arg.rs:314:56
    |
 LL |     fn cow_good_ret_ty<'a>(input: &'a Cow<'a, str>) -> &str {
-   |                                    ^^     ^^           ---- the same lifetime is elided here
+   |                                    --     --           ^^^^ the same lifetime is elided here
    |                                    |      |
    |                                    |      the lifetime is named here
    |                                    the lifetime is named here
diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.fixed b/src/tools/clippy/tests/ui/ptr_as_ptr.fixed
index 71fea6144e7..78e1ceb480a 100644
--- a/src/tools/clippy/tests/ui/ptr_as_ptr.fixed
+++ b/src/tools/clippy/tests/ui/ptr_as_ptr.fixed
@@ -2,8 +2,8 @@
 
 #![warn(clippy::ptr_as_ptr)]
 
-#[macro_use]
 extern crate proc_macros;
+use proc_macros::{external, inline_macros, with_span};
 
 mod issue_11278_a {
     #[derive(Debug)]
@@ -53,11 +53,13 @@ fn main() {
     //~^ ptr_as_ptr
 
     // Make sure the lint is triggered inside a macro
-    let _ = inline!($ptr.cast::<i32>());
-    //~^ ptr_as_ptr
+    // FIXME: `is_from_proc_macro` incorrectly stops the lint from firing here
+    let _ = inline!($ptr as *const i32);
 
     // Do not lint inside macros from external crates
     let _ = external!($ptr as *const i32);
+
+    let _ = with_span!(expr $ptr as *const i32);
 }
 
 #[clippy::msrv = "1.37"]
diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.rs b/src/tools/clippy/tests/ui/ptr_as_ptr.rs
index 4d507592a1e..70732cf0a6c 100644
--- a/src/tools/clippy/tests/ui/ptr_as_ptr.rs
+++ b/src/tools/clippy/tests/ui/ptr_as_ptr.rs
@@ -2,8 +2,8 @@
 
 #![warn(clippy::ptr_as_ptr)]
 
-#[macro_use]
 extern crate proc_macros;
+use proc_macros::{external, inline_macros, with_span};
 
 mod issue_11278_a {
     #[derive(Debug)]
@@ -53,11 +53,13 @@ fn main() {
     //~^ ptr_as_ptr
 
     // Make sure the lint is triggered inside a macro
+    // FIXME: `is_from_proc_macro` incorrectly stops the lint from firing here
     let _ = inline!($ptr as *const i32);
-    //~^ ptr_as_ptr
 
     // Do not lint inside macros from external crates
     let _ = external!($ptr as *const i32);
+
+    let _ = with_span!(expr $ptr as *const i32);
 }
 
 #[clippy::msrv = "1.37"]
diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.stderr b/src/tools/clippy/tests/ui/ptr_as_ptr.stderr
index adad159bb0f..c0a2a4b6d20 100644
--- a/src/tools/clippy/tests/ui/ptr_as_ptr.stderr
+++ b/src/tools/clippy/tests/ui/ptr_as_ptr.stderr
@@ -38,174 +38,166 @@ LL |     let _: *mut i32 = mut_ptr as _;
    |                       ^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:56:21
-   |
-LL |     let _ = inline!($ptr as *const i32);
-   |                     ^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `$ptr.cast::<i32>()`
-   |
-   = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:78:13
+  --> tests/ui/ptr_as_ptr.rs:80:13
    |
 LL |     let _ = ptr as *const i32;
    |             ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::<i32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:80:13
+  --> tests/ui/ptr_as_ptr.rs:82:13
    |
 LL |     let _ = mut_ptr as *mut i32;
    |             ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::<i32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:88:9
+  --> tests/ui/ptr_as_ptr.rs:90:9
    |
 LL |         ptr::null_mut() as *mut u32
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut::<u32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:93:9
+  --> tests/ui/ptr_as_ptr.rs:95:9
    |
 LL |         std::ptr::null_mut() as *mut u32
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut::<u32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:99:9
+  --> tests/ui/ptr_as_ptr.rs:101:9
    |
 LL |         ptr::null_mut() as *mut u32
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut::<u32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:104:9
+  --> tests/ui/ptr_as_ptr.rs:106:9
    |
 LL |         core::ptr::null_mut() as *mut u32
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut::<u32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:110:9
+  --> tests/ui/ptr_as_ptr.rs:112:9
    |
 LL |         ptr::null() as *const u32
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null::<u32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:115:9
+  --> tests/ui/ptr_as_ptr.rs:117:9
    |
 LL |         std::ptr::null() as *const u32
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::<u32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:121:9
+  --> tests/ui/ptr_as_ptr.rs:123:9
    |
 LL |         ptr::null() as *const u32
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null::<u32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:126:9
+  --> tests/ui/ptr_as_ptr.rs:128:9
    |
 LL |         core::ptr::null() as *const u32
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null::<u32>()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:134:9
+  --> tests/ui/ptr_as_ptr.rs:136:9
    |
 LL |         ptr::null_mut() as *mut _
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:139:9
+  --> tests/ui/ptr_as_ptr.rs:141:9
    |
 LL |         std::ptr::null_mut() as *mut _
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:145:9
+  --> tests/ui/ptr_as_ptr.rs:147:9
    |
 LL |         ptr::null_mut() as *mut _
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:150:9
+  --> tests/ui/ptr_as_ptr.rs:152:9
    |
 LL |         core::ptr::null_mut() as *mut _
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:156:9
+  --> tests/ui/ptr_as_ptr.rs:158:9
    |
 LL |         ptr::null() as *const _
    |         ^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:161:9
+  --> tests/ui/ptr_as_ptr.rs:163:9
    |
 LL |         std::ptr::null() as *const _
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:167:9
+  --> tests/ui/ptr_as_ptr.rs:169:9
    |
 LL |         ptr::null() as *const _
    |         ^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:172:9
+  --> tests/ui/ptr_as_ptr.rs:174:9
    |
 LL |         core::ptr::null() as *const _
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:180:9
+  --> tests/ui/ptr_as_ptr.rs:182:9
    |
 LL |         ptr::null_mut() as _
    |         ^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:185:9
+  --> tests/ui/ptr_as_ptr.rs:187:9
    |
 LL |         std::ptr::null_mut() as _
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:191:9
+  --> tests/ui/ptr_as_ptr.rs:193:9
    |
 LL |         ptr::null_mut() as _
    |         ^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:196:9
+  --> tests/ui/ptr_as_ptr.rs:198:9
    |
 LL |         core::ptr::null_mut() as _
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:202:9
+  --> tests/ui/ptr_as_ptr.rs:204:9
    |
 LL |         ptr::null() as _
    |         ^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:207:9
+  --> tests/ui/ptr_as_ptr.rs:209:9
    |
 LL |         std::ptr::null() as _
    |         ^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:213:9
+  --> tests/ui/ptr_as_ptr.rs:215:9
    |
 LL |         ptr::null() as _
    |         ^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:218:9
+  --> tests/ui/ptr_as_ptr.rs:220:9
    |
 LL |         core::ptr::null() as _
    |         ^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null()`
 
 error: `as` casting between raw pointers without changing their constness
-  --> tests/ui/ptr_as_ptr.rs:226:43
+  --> tests/ui/ptr_as_ptr.rs:228:43
    |
 LL |         let _: fn() = std::mem::transmute(std::ptr::null::<()>() as *const u8);
    |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::<u8>()`
 
-error: aborting due to 34 previous errors
+error: aborting due to 33 previous errors
 
diff --git a/src/tools/clippy/tests/ui/similar_names.rs b/src/tools/clippy/tests/ui/similar_names.rs
index 69b6ab6220b..55a141209f0 100644
--- a/src/tools/clippy/tests/ui/similar_names.rs
+++ b/src/tools/clippy/tests/ui/similar_names.rs
@@ -89,6 +89,10 @@ fn main() {
 
     let iter: i32;
     let item: i32;
+
+    // 3 letter names are allowed to be similar
+    let kta: i32;
+    let ktv: i32;
 }
 
 fn foo() {
diff --git a/src/tools/clippy/tests/ui/similar_names.stderr b/src/tools/clippy/tests/ui/similar_names.stderr
index 8d722fb8b56..c226f73d4db 100644
--- a/src/tools/clippy/tests/ui/similar_names.stderr
+++ b/src/tools/clippy/tests/ui/similar_names.stderr
@@ -49,13 +49,13 @@ LL |     let parser: i32;
    |         ^^^^^^
 
 error: binding's name is too similar to existing binding
-  --> tests/ui/similar_names.rs:98:16
+  --> tests/ui/similar_names.rs:102:16
    |
 LL |         bpple: sprang,
    |                ^^^^^^
    |
 note: existing binding defined here
-  --> tests/ui/similar_names.rs:97:16
+  --> tests/ui/similar_names.rs:101:16
    |
 LL |         apple: spring,
    |                ^^^^^^
diff --git a/src/tools/clippy/tests/ui/transmute.rs b/src/tools/clippy/tests/ui/transmute.rs
index e968e7a5924..e7099104f94 100644
--- a/src/tools/clippy/tests/ui/transmute.rs
+++ b/src/tools/clippy/tests/ui/transmute.rs
@@ -4,6 +4,7 @@
     dead_code,
     clippy::borrow_as_ptr,
     unnecessary_transmutes,
+    integer_to_ptr_transmutes,
     clippy::needless_lifetimes,
     clippy::missing_transmute_annotations
 )]
@@ -60,12 +61,10 @@ fn useless() {
         //~^ useless_transmute
 
         let _: *const usize = std::mem::transmute(5_isize);
-        //~^ useless_transmute
 
         let _ = std::ptr::dangling::<usize>();
 
         let _: *const usize = std::mem::transmute(1 + 1usize);
-        //~^ useless_transmute
 
         let _ = (1 + 1_usize) as *const usize;
     }
diff --git a/src/tools/clippy/tests/ui/transmute.stderr b/src/tools/clippy/tests/ui/transmute.stderr
index 79528ec06f1..9478db09481 100644
--- a/src/tools/clippy/tests/ui/transmute.stderr
+++ b/src/tools/clippy/tests/ui/transmute.stderr
@@ -1,5 +1,5 @@
 error: transmute from a reference to a pointer
-  --> tests/ui/transmute.rs:33:27
+  --> tests/ui/transmute.rs:34:27
    |
 LL |         let _: *const T = core::mem::transmute(t);
    |                           ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T`
@@ -8,61 +8,49 @@ LL |         let _: *const T = core::mem::transmute(t);
    = help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]`
 
 error: transmute from a reference to a pointer
-  --> tests/ui/transmute.rs:36:25
+  --> tests/ui/transmute.rs:37:25
    |
 LL |         let _: *mut T = core::mem::transmute(t);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *mut T`
 
 error: transmute from a reference to a pointer
-  --> tests/ui/transmute.rs:39:27
+  --> tests/ui/transmute.rs:40:27
    |
 LL |         let _: *const U = core::mem::transmute(t);
    |                           ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *const U`
 
 error: transmute from a type (`std::vec::Vec<i32>`) to itself
-  --> tests/ui/transmute.rs:47:27
+  --> tests/ui/transmute.rs:48:27
    |
 LL |         let _: Vec<i32> = core::mem::transmute(my_vec());
    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: transmute from a type (`std::vec::Vec<i32>`) to itself
-  --> tests/ui/transmute.rs:50:27
+  --> tests/ui/transmute.rs:51:27
    |
 LL |         let _: Vec<i32> = core::mem::transmute(my_vec());
    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: transmute from a type (`std::vec::Vec<i32>`) to itself
-  --> tests/ui/transmute.rs:53:27
+  --> tests/ui/transmute.rs:54:27
    |
 LL |         let _: Vec<i32> = std::mem::transmute(my_vec());
    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: transmute from a type (`std::vec::Vec<i32>`) to itself
-  --> tests/ui/transmute.rs:56:27
+  --> tests/ui/transmute.rs:57:27
    |
 LL |         let _: Vec<i32> = std::mem::transmute(my_vec());
    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: transmute from a type (`std::vec::Vec<i32>`) to itself
-  --> tests/ui/transmute.rs:59:27
+  --> tests/ui/transmute.rs:60:27
    |
 LL |         let _: Vec<i32> = my_transmute(my_vec());
    |                           ^^^^^^^^^^^^^^^^^^^^^^
 
-error: transmute from an integer to a pointer
-  --> tests/ui/transmute.rs:62:31
-   |
-LL |         let _: *const usize = std::mem::transmute(5_isize);
-   |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `5_isize as *const usize`
-
-error: transmute from an integer to a pointer
-  --> tests/ui/transmute.rs:67:31
-   |
-LL |         let _: *const usize = std::mem::transmute(1 + 1usize);
-   |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(1 + 1usize) as *const usize`
-
 error: transmute from a type (`*const Usize`) to the type that it points to (`Usize`)
-  --> tests/ui/transmute.rs:99:24
+  --> tests/ui/transmute.rs:98:24
    |
 LL |         let _: Usize = core::mem::transmute(int_const_ptr);
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -71,25 +59,25 @@ LL |         let _: Usize = core::mem::transmute(int_const_ptr);
    = help: to override `-D warnings` add `#[allow(clippy::crosspointer_transmute)]`
 
 error: transmute from a type (`*mut Usize`) to the type that it points to (`Usize`)
-  --> tests/ui/transmute.rs:102:24
+  --> tests/ui/transmute.rs:101:24
    |
 LL |         let _: Usize = core::mem::transmute(int_mut_ptr);
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: transmute from a type (`Usize`) to a pointer to that type (`*const Usize`)
-  --> tests/ui/transmute.rs:105:31
+  --> tests/ui/transmute.rs:104:31
    |
 LL |         let _: *const Usize = core::mem::transmute(my_int());
    |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: transmute from a type (`Usize`) to a pointer to that type (`*mut Usize`)
-  --> tests/ui/transmute.rs:108:29
+  --> tests/ui/transmute.rs:107:29
    |
 LL |         let _: *mut Usize = core::mem::transmute(my_int());
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: transmute from a `u8` to a `bool`
-  --> tests/ui/transmute.rs:115:28
+  --> tests/ui/transmute.rs:114:28
    |
 LL |     let _: bool = unsafe { std::mem::transmute(0_u8) };
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `0_u8 != 0`
@@ -98,7 +86,7 @@ LL |     let _: bool = unsafe { std::mem::transmute(0_u8) };
    = help: to override `-D warnings` add `#[allow(clippy::transmute_int_to_bool)]`
 
 error: transmute from a `&[u8]` to a `&str`
-  --> tests/ui/transmute.rs:122:28
+  --> tests/ui/transmute.rs:121:28
    |
 LL |     let _: &str = unsafe { std::mem::transmute(B) };
    |                            ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8(B).unwrap()`
@@ -107,16 +95,16 @@ LL |     let _: &str = unsafe { std::mem::transmute(B) };
    = help: to override `-D warnings` add `#[allow(clippy::transmute_bytes_to_str)]`
 
 error: transmute from a `&mut [u8]` to a `&mut str`
-  --> tests/ui/transmute.rs:125:32
+  --> tests/ui/transmute.rs:124:32
    |
 LL |     let _: &mut str = unsafe { std::mem::transmute(mb) };
    |                                ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_mut(mb).unwrap()`
 
 error: transmute from a `&[u8]` to a `&str`
-  --> tests/ui/transmute.rs:128:30
+  --> tests/ui/transmute.rs:127:30
    |
 LL |     const _: &str = unsafe { std::mem::transmute(B) };
    |                              ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_unchecked(B)`
 
-error: aborting due to 18 previous errors
+error: aborting due to 16 previous errors
 
diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed
index e7ad2a1cbbc..02f67f79e2b 100644
--- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed
+++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed
@@ -13,9 +13,6 @@ fn main() {
     // We should see an error message for each transmute, and no error messages for
     // the casts, since the casts are the recommended fixes.
 
-    // e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast
-    let _ptr_i32_transmute = unsafe { usize::MAX as *const i32 };
-    //~^ useless_transmute
     let ptr_i32 = usize::MAX as *const i32;
 
     // e has type *T, U is *U_0, and either U_0: Sized ...
diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs
index 42a81777a82..c5e156405eb 100644
--- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs
+++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs
@@ -13,9 +13,6 @@ fn main() {
     // We should see an error message for each transmute, and no error messages for
     // the casts, since the casts are the recommended fixes.
 
-    // e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast
-    let _ptr_i32_transmute = unsafe { transmute::<usize, *const i32>(usize::MAX) };
-    //~^ useless_transmute
     let ptr_i32 = usize::MAX as *const i32;
 
     // e has type *T, U is *U_0, and either U_0: Sized ...
diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr
index 7746f087cc7..f39a64d57eb 100644
--- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr
+++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr
@@ -1,14 +1,5 @@
-error: transmute from an integer to a pointer
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:17:39
-   |
-LL |     let _ptr_i32_transmute = unsafe { transmute::<usize, *const i32>(usize::MAX) };
-   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `usize::MAX as *const i32`
-   |
-   = note: `-D clippy::useless-transmute` implied by `-D warnings`
-   = help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]`
-
 error: transmute from a pointer to a pointer
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:22:38
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:19:38
    |
 LL |     let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr_i32) };
    |                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -22,7 +13,7 @@ LL +     let _ptr_i8_transmute = unsafe { ptr_i32.cast::<i8>() };
    |
 
 error: transmute from a pointer to a pointer
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:29:46
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:26:46
    |
 LL |     let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
    |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -34,7 +25,7 @@ LL +     let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u32] };
    |
 
 error: transmute from `*const i32` to `usize` which could be expressed as a pointer cast instead
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:36:50
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:33:50
    |
 LL |     let _usize_from_int_ptr_transmute = unsafe { transmute::<*const i32, usize>(ptr_i32) };
    |                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr_i32 as usize`
@@ -43,40 +34,43 @@ LL |     let _usize_from_int_ptr_transmute = unsafe { transmute::<*const i32, us
    = help: to override `-D warnings` add `#[allow(clippy::transmutes_expressible_as_ptr_casts)]`
 
 error: transmute from a reference to a pointer
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:43:41
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:40:41
    |
 LL |     let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) };
    |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `array_ref as *const [i32; 4]`
+   |
+   = note: `-D clippy::useless-transmute` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]`
 
 error: transmute from `fn(usize) -> u8` to `*const usize` which could be expressed as a pointer cast instead
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:52:41
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:49:41
    |
 LL |     let _usize_ptr_transmute = unsafe { transmute::<fn(usize) -> u8, *const usize>(foo) };
    |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as *const usize`
 
 error: transmute from `fn(usize) -> u8` to `usize` which could be expressed as a pointer cast instead
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:57:49
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:54:49
    |
 LL |     let _usize_from_fn_ptr_transmute = unsafe { transmute::<fn(usize) -> u8, usize>(foo) };
    |                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as usize`
 
 error: transmute from `*const u32` to `usize` which could be expressed as a pointer cast instead
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:61:36
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:58:36
    |
 LL |     let _usize_from_ref = unsafe { transmute::<*const u32, usize>(&1u32) };
    |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&1u32 as *const u32 as usize`
 
 error: transmute from a reference to a pointer
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:73:14
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:70:14
    |
 LL |     unsafe { transmute::<&[i32; 1], *const u8>(in_param) }
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `in_param as *const [i32; 1] as *const u8`
 
 error: transmute from `fn()` to `*const u8` which could be expressed as a pointer cast instead
-  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:92:28
+  --> tests/ui/transmutes_expressible_as_ptr_casts.rs:89:28
    |
 LL |     let _x: u8 = unsafe { *std::mem::transmute::<fn(), *const u8>(f) };
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(f as *const u8)`
 
-error: aborting due to 10 previous errors
+error: aborting due to 9 previous errors
 
diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.fixed b/src/tools/clippy/tests/ui/unnecessary_operation.fixed
index ac9fa4de20a..db5409bc491 100644
--- a/src/tools/clippy/tests/ui/unnecessary_operation.fixed
+++ b/src/tools/clippy/tests/ui/unnecessary_operation.fixed
@@ -78,25 +78,25 @@ fn main() {
     //~^ unnecessary_operation
     get_number();
     //~^ unnecessary_operation
-    5;get_number();
+    5; get_number();
     //~^ unnecessary_operation
     get_number();
     //~^ unnecessary_operation
     get_number();
     //~^ unnecessary_operation
-    5;6;get_number();
+    5; 6; get_number();
     //~^ unnecessary_operation
     get_number();
     //~^ unnecessary_operation
     get_number();
     //~^ unnecessary_operation
-    5;get_number();
+    5; get_number();
     //~^ unnecessary_operation
-    42;get_number();
+    42; get_number();
     //~^ unnecessary_operation
     assert!([42, 55].len() > get_usize());
     //~^ unnecessary_operation
-    42;get_number();
+    42; get_number();
     //~^ unnecessary_operation
     get_number();
     //~^ unnecessary_operation
diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.stderr b/src/tools/clippy/tests/ui/unnecessary_operation.stderr
index 0fda1dfde19..3439ba2e33e 100644
--- a/src/tools/clippy/tests/ui/unnecessary_operation.stderr
+++ b/src/tools/clippy/tests/ui/unnecessary_operation.stderr
@@ -35,7 +35,7 @@ error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:81:5
    |
 LL |     5 + get_number();
-   |     ^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;get_number();`
+   |     ^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5; get_number();`
 
 error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:83:5
@@ -53,7 +53,7 @@ error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:87:5
    |
 LL |     (5, 6, get_number());
-   |     ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;6;get_number();`
+   |     ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5; 6; get_number();`
 
 error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:89:5
@@ -71,13 +71,13 @@ error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:93:5
    |
 LL |     5..get_number();
-   |     ^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;get_number();`
+   |     ^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5; get_number();`
 
 error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:95:5
    |
 LL |     [42, get_number()];
-   |     ^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42;get_number();`
+   |     ^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42; get_number();`
 
 error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:97:5
@@ -89,7 +89,7 @@ error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:99:5
    |
 LL |     (42, get_number()).1;
-   |     ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42;get_number();`
+   |     ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42; get_number();`
 
 error: unnecessary operation
   --> tests/ui/unnecessary_operation.rs:101:5
diff --git a/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed b/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed
index f10d804c8cc..797f1505f49 100644
--- a/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed
+++ b/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed
@@ -63,3 +63,12 @@ fn issue14100() -> bool {
     // cast into the `bool` function return type.
     if return true {};
 }
+
+fn issue15426(x: u32) {
+    // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs
+    #[rustfmt::skip]
+    match x {
+        0b00 => {}  0b01 => {}
+        0b11 => {}  _    => {}
+    };
+}
diff --git a/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed b/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed
index 32a3bb9b408..d2609cea000 100644
--- a/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed
+++ b/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed
@@ -63,3 +63,12 @@ fn issue14100() -> bool {
     // cast into the `bool` function return type.
     if return true {};
 }
+
+fn issue15426(x: u32) {
+    // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs
+    #[rustfmt::skip]
+    match x {
+        0b00 => {}  0b01 => {}
+        0b11 => {}  _    => {}
+    };
+}
diff --git a/src/tools/clippy/tests/ui/unnecessary_semicolon.rs b/src/tools/clippy/tests/ui/unnecessary_semicolon.rs
index 91b28218022..55f1ec84cb0 100644
--- a/src/tools/clippy/tests/ui/unnecessary_semicolon.rs
+++ b/src/tools/clippy/tests/ui/unnecessary_semicolon.rs
@@ -63,3 +63,12 @@ fn issue14100() -> bool {
     // cast into the `bool` function return type.
     if return true {};
 }
+
+fn issue15426(x: u32) {
+    // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs
+    #[rustfmt::skip]
+    match x {
+        0b00 => {}  0b01 => {}
+        0b11 => {}  _    => {}
+    };
+}
diff --git a/src/tools/clippy/tests/ui/zero_sized_hashmap_values.rs b/src/tools/clippy/tests/ui/zero_sized_hashmap_values.rs
index dcbfd16843d..ee2fd19b5ee 100644
--- a/src/tools/clippy/tests/ui/zero_sized_hashmap_values.rs
+++ b/src/tools/clippy/tests/ui/zero_sized_hashmap_values.rs
@@ -92,6 +92,14 @@ fn issue14822() {
     //~^ zero_sized_map_values
 }
 
+fn issue15429() {
+    struct E<'a>(&'a [E<'a>]);
+
+    // The assertion error happens when the type being evaluated has escaping bound vars
+    // as it cannot be wrapped in a dummy binder during size computation.
+    type F = dyn for<'a> FnOnce(HashMap<u32, E<'a>>) -> u32;
+}
+
 fn main() {
     let _: HashMap<String, ()> = HashMap::new();
     //~^ zero_sized_map_values
diff --git a/src/tools/clippy/tests/ui/zero_sized_hashmap_values.stderr b/src/tools/clippy/tests/ui/zero_sized_hashmap_values.stderr
index d29491fa05c..52ffef280c1 100644
--- a/src/tools/clippy/tests/ui/zero_sized_hashmap_values.stderr
+++ b/src/tools/clippy/tests/ui/zero_sized_hashmap_values.stderr
@@ -89,7 +89,7 @@ LL |     type D = HashMap<u32, S<E>>;
    = help: consider using a set instead
 
 error: map with zero-sized value type
-  --> tests/ui/zero_sized_hashmap_values.rs:96:34
+  --> tests/ui/zero_sized_hashmap_values.rs:104:34
    |
 LL |     let _: HashMap<String, ()> = HashMap::new();
    |                                  ^^^^^^^
@@ -97,7 +97,7 @@ LL |     let _: HashMap<String, ()> = HashMap::new();
    = help: consider using a set instead
 
 error: map with zero-sized value type
-  --> tests/ui/zero_sized_hashmap_values.rs:96:12
+  --> tests/ui/zero_sized_hashmap_values.rs:104:12
    |
 LL |     let _: HashMap<String, ()> = HashMap::new();
    |            ^^^^^^^^^^^^^^^^^^^
@@ -105,7 +105,7 @@ LL |     let _: HashMap<String, ()> = HashMap::new();
    = help: consider using a set instead
 
 error: map with zero-sized value type
-  --> tests/ui/zero_sized_hashmap_values.rs:102:12
+  --> tests/ui/zero_sized_hashmap_values.rs:110:12
    |
 LL |     let _: HashMap<_, _> = std::iter::empty::<(String, ())>().collect();
    |            ^^^^^^^^^^^^^
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index 2d49b1a7097..7fc80c1edb1 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -692,7 +692,9 @@ pub struct Config {
     pub minicore_path: Utf8PathBuf,
 
     /// Current codegen backend used.
-    pub codegen_backend: CodegenBackend,
+    pub default_codegen_backend: CodegenBackend,
+    /// Name/path of the backend to use instead of `default_codegen_backend`.
+    pub override_codegen_backend: Option<String>,
 }
 
 impl Config {
@@ -796,7 +798,8 @@ impl Config {
             profiler_runtime: Default::default(),
             diff_command: Default::default(),
             minicore_path: Default::default(),
-            codegen_backend: CodegenBackend::Llvm,
+            default_codegen_backend: CodegenBackend::Llvm,
+            override_codegen_backend: None,
         }
     }
 
diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs
index 13e694b7f03..f2ad049d526 100644
--- a/src/tools/compiletest/src/directives.rs
+++ b/src/tools/compiletest/src/directives.rs
@@ -205,6 +205,8 @@ pub struct TestProps {
     pub dont_require_annotations: HashSet<ErrorKind>,
     /// Whether pretty printers should be disabled in gdb.
     pub disable_gdb_pretty_printers: bool,
+    /// Compare the output by lines, rather than as a single string.
+    pub compare_output_by_lines: bool,
 }
 
 mod directives {
@@ -254,6 +256,7 @@ mod directives {
     // This isn't a real directive, just one that is probably mistyped often
     pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags";
     pub const DISABLE_GDB_PRETTY_PRINTERS: &'static str = "disable-gdb-pretty-printers";
+    pub const COMPARE_OUTPUT_BY_LINES: &'static str = "compare-output-by-lines";
 }
 
 impl TestProps {
@@ -310,6 +313,7 @@ impl TestProps {
             add_core_stubs: false,
             dont_require_annotations: Default::default(),
             disable_gdb_pretty_printers: false,
+            compare_output_by_lines: false,
         }
     }
 
@@ -664,6 +668,11 @@ impl TestProps {
                         DISABLE_GDB_PRETTY_PRINTERS,
                         &mut self.disable_gdb_pretty_printers,
                     );
+                    config.set_name_directive(
+                        ln,
+                        COMPARE_OUTPUT_BY_LINES,
+                        &mut self.compare_output_by_lines,
+                    );
                 },
             );
 
@@ -1624,7 +1633,7 @@ fn ignore_backends(
                 }
             }
         }) {
-            if config.codegen_backend == backend {
+            if config.default_codegen_backend == backend {
                 return IgnoreDecision::Ignore {
                     reason: format!("{} backend is marked as ignore", backend.as_str()),
                 };
@@ -1651,12 +1660,12 @@ fn needs_backends(
                     panic!("Invalid needs-backends value `{backend}` in `{path}`: {error}")
                 }
             })
-            .any(|backend| config.codegen_backend == backend)
+            .any(|backend| config.default_codegen_backend == backend)
         {
             return IgnoreDecision::Ignore {
                 reason: format!(
                     "{} backend is not part of required backends",
-                    config.codegen_backend.as_str()
+                    config.default_codegen_backend.as_str()
                 ),
             };
         }
diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs
index f7955429d83..0ef84fb4594 100644
--- a/src/tools/compiletest/src/directives/directive_names.rs
+++ b/src/tools/compiletest/src/directives/directive_names.rs
@@ -17,6 +17,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "check-run-results",
     "check-stdout",
     "check-test-line-numbers-match",
+    "compare-output-by-lines",
     "compile-flags",
     "disable-gdb-pretty-printers",
     "doc-flags",
@@ -194,6 +195,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "only-bpf",
     "only-cdb",
     "only-dist",
+    "only-eabihf",
     "only-elf",
     "only-emscripten",
     "only-gnu",
diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs
index f5409e78341..469dd68207e 100644
--- a/src/tools/compiletest/src/lib.rs
+++ b/src/tools/compiletest/src/lib.rs
@@ -212,9 +212,15 @@ pub fn parse_config(args: Vec<String>) -> Config {
         )
         .optopt(
             "",
-            "codegen-backend",
+            "default-codegen-backend",
             "the codegen backend currently used",
             "CODEGEN BACKEND NAME",
+        )
+        .optopt(
+            "",
+            "override-codegen-backend",
+            "the codegen backend to use instead of the default one",
+            "CODEGEN BACKEND [NAME | PATH]",
         );
 
     let (argv0, args_) = args.split_first().unwrap();
@@ -276,14 +282,17 @@ pub fn parse_config(args: Vec<String>) -> Config {
             || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
         );
 
-    let codegen_backend = match matches.opt_str("codegen-backend").as_deref() {
+    let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
         Some(backend) => match CodegenBackend::try_from(backend) {
             Ok(backend) => backend,
-            Err(error) => panic!("invalid value `{backend}` for `--codegen-backend`: {error}"),
+            Err(error) => {
+                panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
+            }
         },
         // By default, it's always llvm.
         None => CodegenBackend::Llvm,
     };
+    let override_codegen_backend = matches.opt_str("override-codegen-backend");
 
     let run_ignored = matches.opt_present("ignored");
     let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
@@ -472,7 +481,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
 
         minicore_path: opt_path(matches, "minicore-path"),
 
-        codegen_backend,
+        default_codegen_backend,
+        override_codegen_backend,
     }
 }
 
@@ -812,13 +822,13 @@ fn collect_tests_from_dir(
         && let Some(Utf8Component::Normal(parent)) = components.next()
         && parent == "tests"
         && let Ok(backend) = CodegenBackend::try_from(backend)
-        && backend != cx.config.codegen_backend
+        && backend != cx.config.default_codegen_backend
     {
         // We ignore asm tests which don't match the current codegen backend.
         warning!(
             "Ignoring tests in `{dir}` because they don't match the configured codegen \
              backend (`{}`)",
-            cx.config.codegen_backend.as_str(),
+            cx.config.default_codegen_backend.as_str(),
         );
         return Ok(TestCollector::new());
     }
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index be4663fffbe..739db8fa095 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -1558,6 +1558,11 @@ impl<'test> TestCx<'test> {
             rustc.arg("--sysroot").arg(&self.config.sysroot_base);
         }
 
+        // If the provided codegen backend is not LLVM, we need to pass it.
+        if let Some(ref backend) = self.config.override_codegen_backend {
+            rustc.arg(format!("-Zcodegen-backend={}", backend));
+        }
+
         // Optionally prevent default --target if specified in test compile-flags.
         let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target"));
 
@@ -2151,7 +2156,7 @@ impl<'test> TestCx<'test> {
 
         #[rustfmt::skip]
         let tidy_args = [
-            "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar",
+            "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar,rustdoc-topbar",
             "--indent", "yes",
             "--indent-spaces", "2",
             "--wrap", "0",
@@ -2749,7 +2754,11 @@ impl<'test> TestCx<'test> {
         // Wrapper tools set by `runner` might provide extra output on failure,
         // for example a WebAssembly runtime might print the stack trace of an
         // `unreachable` instruction by default.
-        let compare_output_by_lines = self.config.runner.is_some();
+        //
+        // Also, some tests like `ui/parallel-rustc` have non-deterministic
+        // orders of output, so we need to compare by lines.
+        let compare_output_by_lines =
+            self.props.compare_output_by_lines || self.config.runner.is_some();
 
         let tmp;
         let (expected, actual): (&str, &str) = if compare_output_by_lines {
diff --git a/src/tools/html-checker/main.rs b/src/tools/html-checker/main.rs
index 5cdc4d53ab5..d5335d9e72e 100644
--- a/src/tools/html-checker/main.rs
+++ b/src/tools/html-checker/main.rs
@@ -31,7 +31,7 @@ fn check_html_file(file: &Path) -> usize {
         .arg("--mute-id") // this option is useful in case we want to mute more warnings
         .arg("yes")
         .arg("--new-blocklevel-tags")
-        .arg("rustdoc-search,rustdoc-toolbar") // custom elements
+        .arg("rustdoc-search,rustdoc-toolbar,rustdoc-topbar") // custom elements
         .arg("--mute")
         .arg(&to_mute_s)
         .arg(file);
diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml
index c47f9695624..7d79c384f85 100644
--- a/src/tools/miri/.github/workflows/ci.yml
+++ b/src/tools/miri/.github/workflows/ci.yml
@@ -69,12 +69,6 @@ jobs:
           sudo apt update
           # Install needed packages
           sudo apt install $(echo "libatomic1: zlib1g-dev:" | sed 's/:/:${{ matrix.multiarch }}/g')
-      - name: Install rustup on Windows ARM
-        if: ${{ matrix.os == 'windows-11-arm' }}
-        run: |
-            curl -LOs https://static.rust-lang.org/rustup/dist/aarch64-pc-windows-msvc/rustup-init.exe
-            ./rustup-init.exe -y --no-modify-path
-            echo "$USERPROFILE/.cargo/bin" >> "$GITHUB_PATH"
       - uses: ./.github/workflows/setup
         with:
           toolchain_flags: "--host ${{ matrix.host_target }}"
@@ -169,7 +163,13 @@ jobs:
         run: rustup toolchain install nightly --profile minimal
       - name: Install rustup-toolchain-install-master
         run: cargo install -f rustup-toolchain-install-master
-      - name: Push changes to a branch and create PR
+      # Create a token for the next step so it can create a PR that actually runs CI.
+      - uses: actions/create-github-app-token@v2
+        id: app-token
+        with:
+          app-id: ${{ vars.APP_CLIENT_ID }}
+          private-key: ${{ secrets.APP_PRIVATE_KEY }}
+      - name: pull changes from rustc and create PR
         run: |
           # Make it easier to see what happens.
           set -x
@@ -198,9 +198,9 @@ jobs:
           BRANCH="rustup-$(date -u +%Y-%m-%d)"
           git switch -c $BRANCH
           git push -u origin $BRANCH
-          gh pr create -B master --title 'Automatic Rustup' --body 'Please close and re-open this PR to trigger CI, then enable auto-merge.'
+          gh pr create -B master --title 'Automatic Rustup' --body "Update \`rustc\` to https://github.com/rust-lang/rust/commit/$(cat rust-version)."
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
 
   cron-fail-notify:
     name: cronjob failure notification
diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml
index 91dadf78a2f..99111092d39 100644
--- a/src/tools/miri/Cargo.toml
+++ b/src/tools/miri/Cargo.toml
@@ -78,6 +78,7 @@ native-lib = ["dep:libffi", "dep:libloading", "dep:capstone", "dep:ipc-channel",
 
 [lints.rust.unexpected_cfgs]
 level = "warn"
+check-cfg = ['cfg(bootstrap)']
 
 # Be aware that this file is inside a workspace when used via the
 # submodule in the rustc repo. That means there are many cargo features
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index 2178caf6396..59adc572eaa 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-733dab558992d902d6d17576de1da768094e2cf3
+f605b57042ffeb320d7ae44490113a827139b766
diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs
index 9ecbd31c5b9..b5ca9601547 100644
--- a/src/tools/miri/src/diagnostics.rs
+++ b/src/tools/miri/src/diagnostics.rs
@@ -282,7 +282,8 @@ pub fn report_error<'tcx>(
             },
             TreeBorrowsUb { title: _, details, history } => {
                 let mut helps = vec![
-                    note!("this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental")
+                    note!("this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental"),
+                    note!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information"),
                 ];
                 for m in details {
                     helps.push(note!("{m}"));
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index 49021b1c4a2..0b2ce900414 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -12,10 +12,11 @@ use rand::rngs::StdRng;
 use rand::{Rng, SeedableRng};
 use rustc_abi::{Align, ExternAbi, Size};
 use rustc_apfloat::{Float, FloatConvert};
-use rustc_hir::attrs::InlineAttr;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 #[allow(unused)]
 use rustc_data_structures::static_assert_size;
+use rustc_hir::attrs::InlineAttr;
+use rustc_middle::middle::codegen_fn_attrs::TargetFeatureKind;
 use rustc_middle::mir;
 use rustc_middle::query::TyCtxtAt;
 use rustc_middle::ty::layout::{
@@ -1076,7 +1077,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
                 .target_features
                 .iter()
                 .filter(|&feature| {
-                    !feature.implied && !ecx.tcx.sess.target_features.contains(&feature.name)
+                    feature.kind != TargetFeatureKind::Implied && !ecx.tcx.sess.target_features.contains(&feature.name)
                 })
                 .fold(String::new(), |mut s, feature| {
                     if !s.is_empty() {
@@ -1111,6 +1112,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
     ) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
         // For foreign items, try to see if we can emulate them.
         if ecx.tcx.is_foreign_item(instance.def_id()) {
+            let _trace = enter_trace_span!("emulate_foreign_item");
             // An external function call that does not have a MIR body. We either find MIR elsewhere
             // or emulate its effect.
             // This will be Ok(None) if we're emulating the intrinsic entirely within Miri (no need
@@ -1123,6 +1125,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
         }
 
         // Otherwise, load the MIR.
+        let _trace = enter_trace_span!("load_mir");
         interp_ok(Some((ecx.load_mir(instance.def, None)?, instance)))
     }
 
@@ -1394,6 +1397,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
             GlobalDataRaceHandler::Genmc(genmc_ctx) =>
                 genmc_ctx.memory_load(machine, ptr.addr(), range.size)?,
             GlobalDataRaceHandler::Vclocks(_data_race) => {
+                let _trace = enter_trace_span!(data_race::before_memory_read);
                 let AllocDataRaceHandler::Vclocks(data_race, weak_memory) = &alloc_extra.data_race
                 else {
                     unreachable!();
@@ -1429,6 +1433,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
                 genmc_ctx.memory_store(machine, ptr.addr(), range.size)?;
             }
             GlobalDataRaceHandler::Vclocks(_global_state) => {
+                let _trace = enter_trace_span!(data_race::before_memory_write);
                 let AllocDataRaceHandler::Vclocks(data_race, weak_memory) =
                     &mut alloc_extra.data_race
                 else {
@@ -1465,6 +1470,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
             GlobalDataRaceHandler::Genmc(genmc_ctx) =>
                 genmc_ctx.handle_dealloc(machine, ptr.addr(), size, align, kind)?,
             GlobalDataRaceHandler::Vclocks(_global_state) => {
+                let _trace = enter_trace_span!(data_race::before_memory_deallocation);
                 let data_race = alloc_extra.data_race.as_vclocks_mut().unwrap();
                 data_race.write(
                     alloc_id,
@@ -1675,6 +1681,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
         local: mir::Local,
     ) -> InterpResult<'tcx> {
         if let Some(data_race) = &frame.extra.data_race {
+            let _trace = enter_trace_span!(data_race::after_local_read);
             data_race.local_read(local, &ecx.machine);
         }
         interp_ok(())
@@ -1686,6 +1693,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
         storage_live: bool,
     ) -> InterpResult<'tcx> {
         if let Some(data_race) = &ecx.frame().extra.data_race {
+            let _trace = enter_trace_span!(data_race::after_local_write);
             data_race.local_write(local, storage_live, &ecx.machine);
         }
         interp_ok(())
@@ -1708,6 +1716,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
         if let Some(data_race) =
             &machine.threads.active_thread_stack().last().unwrap().extra.data_race
         {
+            let _trace = enter_trace_span!(data_race::after_local_moved_to_memory);
             data_race.local_moved_to_memory(
                 local,
                 alloc_info.data_race.as_vclocks_mut().unwrap(),
diff --git a/src/tools/miri/src/operator.rs b/src/tools/miri/src/operator.rs
index 3c3f2c28535..116f45f18dd 100644
--- a/src/tools/miri/src/operator.rs
+++ b/src/tools/miri/src/operator.rs
@@ -57,7 +57,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let ptr = left.to_scalar().to_pointer(this)?;
                 // We do the actual operation with usize-typed scalars.
                 let left = ImmTy::from_uint(ptr.addr().bytes(), this.machine.layouts.usize);
-                let result = this.binary_op(bin_op, &left, &right)?;
+                let result = this.binary_op(bin_op, &left, right)?;
                 // Construct a new pointer with the provenance of `ptr` (the LHS).
                 let result_ptr = Pointer::new(
                     ptr.provenance,
diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs
index 49c0c380a08..c2527bf8e25 100644
--- a/src/tools/miri/src/shims/extern_static.rs
+++ b/src/tools/miri/src/shims/extern_static.rs
@@ -62,7 +62,7 @@ impl<'tcx> MiriMachine<'tcx> {
             }
             "android" => {
                 Self::null_ptr_extern_statics(ecx, &["bsd_signal"])?;
-                Self::weak_symbol_extern_statics(ecx, &["signal", "getrandom"])?;
+                Self::weak_symbol_extern_statics(ecx, &["signal", "getrandom", "gettid"])?;
             }
             "windows" => {
                 // "_tls_used"
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index 21545b68029..a700644b95d 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -5,6 +5,7 @@ use std::path::Path;
 use rustc_abi::{Align, AlignFromBytesError, CanonAbi, Size};
 use rustc_apfloat::Float;
 use rustc_ast::expand::allocator::alloc_error_handler_name;
+use rustc_hir::attrs::Linkage;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::CrateNum;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
@@ -138,7 +139,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             Entry::Occupied(e) => e.into_mut(),
             Entry::Vacant(e) => {
                 // Find it if it was not cached.
-                let mut instance_and_crate: Option<(ty::Instance<'_>, CrateNum)> = None;
+
+                struct SymbolTarget<'tcx> {
+                    instance: ty::Instance<'tcx>,
+                    cnum: CrateNum,
+                    is_weak: bool,
+                }
+                let mut symbol_target: Option<SymbolTarget<'tcx>> = None;
                 helpers::iter_exported_symbols(tcx, |cnum, def_id| {
                     let attrs = tcx.codegen_fn_attrs(def_id);
                     // Skip over imports of items.
@@ -146,7 +153,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                         return interp_ok(());
                     }
                     // Skip over items without an explicitly defined symbol name.
-                    if !(attrs.export_name.is_some()
+                    if !(attrs.symbol_name.is_some()
                         || attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE)
                         || attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL))
                     {
@@ -155,40 +162,80 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
                     let instance = Instance::mono(tcx, def_id);
                     let symbol_name = tcx.symbol_name(instance).name;
+                    let is_weak = attrs.linkage == Some(Linkage::WeakAny);
                     if symbol_name == link_name.as_str() {
-                        if let Some((original_instance, original_cnum)) = instance_and_crate {
-                            // Make sure we are consistent wrt what is 'first' and 'second'.
-                            let original_span = tcx.def_span(original_instance.def_id()).data();
-                            let span = tcx.def_span(def_id).data();
-                            if original_span < span {
-                                throw_machine_stop!(TerminationInfo::MultipleSymbolDefinitions {
-                                    link_name,
-                                    first: original_span,
-                                    first_crate: tcx.crate_name(original_cnum),
-                                    second: span,
-                                    second_crate: tcx.crate_name(cnum),
-                                });
-                            } else {
-                                throw_machine_stop!(TerminationInfo::MultipleSymbolDefinitions {
-                                    link_name,
-                                    first: span,
-                                    first_crate: tcx.crate_name(cnum),
-                                    second: original_span,
-                                    second_crate: tcx.crate_name(original_cnum),
-                                });
+                        if let Some(original) = &symbol_target {
+                            // There is more than one definition with this name. What we do now
+                            // depends on whether one or both definitions are weak.
+                            match (is_weak, original.is_weak) {
+                                (false, true) => {
+                                    // Original definition is a weak definition. Override it.
+
+                                    symbol_target = Some(SymbolTarget {
+                                        instance: ty::Instance::mono(tcx, def_id),
+                                        cnum,
+                                        is_weak,
+                                    });
+                                }
+                                (true, false) => {
+                                    // Current definition is a weak definition. Keep the original one.
+                                }
+                                (true, true) | (false, false) => {
+                                    // Either both definitions are non-weak or both are weak. In
+                                    // either case return an error. For weak definitions we error
+                                    // because it is unspecified which definition would have been
+                                    // picked by the linker.
+
+                                    // Make sure we are consistent wrt what is 'first' and 'second'.
+                                    let original_span =
+                                        tcx.def_span(original.instance.def_id()).data();
+                                    let span = tcx.def_span(def_id).data();
+                                    if original_span < span {
+                                        throw_machine_stop!(
+                                            TerminationInfo::MultipleSymbolDefinitions {
+                                                link_name,
+                                                first: original_span,
+                                                first_crate: tcx.crate_name(original.cnum),
+                                                second: span,
+                                                second_crate: tcx.crate_name(cnum),
+                                            }
+                                        );
+                                    } else {
+                                        throw_machine_stop!(
+                                            TerminationInfo::MultipleSymbolDefinitions {
+                                                link_name,
+                                                first: span,
+                                                first_crate: tcx.crate_name(cnum),
+                                                second: original_span,
+                                                second_crate: tcx.crate_name(original.cnum),
+                                            }
+                                        );
+                                    }
+                                }
                             }
+                        } else {
+                            symbol_target = Some(SymbolTarget {
+                                instance: ty::Instance::mono(tcx, def_id),
+                                cnum,
+                                is_weak,
+                            });
                         }
-                        if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
-                            throw_ub_format!(
-                                "attempt to call an exported symbol that is not defined as a function"
-                            );
-                        }
-                        instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum));
                     }
                     interp_ok(())
                 })?;
 
-                e.insert(instance_and_crate.map(|ic| ic.0))
+                // Once we identified the instance corresponding to the symbol, ensure
+                // it is a function. It is okay to encounter non-functions in the search above
+                // as long as the final instance we arrive at is a function.
+                if let Some(SymbolTarget { instance, .. }) = symbol_target {
+                    if !matches!(tcx.def_kind(instance.def_id()), DefKind::Fn | DefKind::AssocFn) {
+                        throw_ub_format!(
+                            "attempt to call an exported symbol that is not defined as a function"
+                        );
+                    }
+                }
+
+                e.insert(symbol_target.map(|SymbolTarget { instance, .. }| instance))
             }
         };
         match instance {
diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs
index 36c18379cb8..74b9b704fea 100644
--- a/src/tools/miri/src/shims/native_lib/mod.rs
+++ b/src/tools/miri/src/shims/native_lib/mod.rs
@@ -242,13 +242,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
                 match evt {
                     AccessEvent::Read(_) => {
-                        // FIXME: ProvenanceMap should have something like get_range().
-                        let p_map = alloc.provenance();
-                        for idx in overlap {
-                            // If a provenance was read by the foreign code, expose it.
-                            if let Some((prov, _idx)) = p_map.get_byte(Size::from_bytes(idx), this) {
-                                this.expose_provenance(prov)?;
-                            }
+                        // If a provenance was read by the foreign code, expose it.
+                        for prov in alloc.provenance().get_range(this, overlap.into()) {
+                            this.expose_provenance(prov)?;
                         }
                     }
                     AccessEvent::Write(_, certain) => {
diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs
index 04c5d28838b..6cb0d221fc0 100644
--- a/src/tools/miri/src/shims/unix/android/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs
@@ -4,13 +4,14 @@ use rustc_span::Symbol;
 use rustc_target::callconv::FnAbi;
 
 use crate::shims::unix::android::thread::prctl;
+use crate::shims::unix::env::EvalContextExt as _;
 use crate::shims::unix::linux_like::epoll::EvalContextExt as _;
 use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
 use crate::shims::unix::linux_like::syscall::syscall;
 use crate::*;
 
-pub fn is_dyn_sym(_name: &str) -> bool {
-    false
+pub fn is_dyn_sym(name: &str) -> bool {
+    matches!(name, "gettid")
 }
 
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -54,6 +55,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
 
+            "gettid" => {
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
+                let result = this.unix_gettid(link_name.as_str())?;
+                this.write_scalar(result, dest)?;
+            }
+
             // Dynamically invoked syscalls
             "syscall" => syscall(this, link_name, abi, args, dest)?,
 
diff --git a/src/tools/miri/test-cargo-miri/run-test.py b/src/tools/miri/test-cargo-miri/run-test.py
index 40bfe7f845f..6b3b6343b82 100755
--- a/src/tools/miri/test-cargo-miri/run-test.py
+++ b/src/tools/miri/test-cargo-miri/run-test.py
@@ -37,7 +37,7 @@ def cargo_miri(cmd, quiet = True, targets = None):
 
 def normalize_stdout(str):
     str = str.replace("src\\", "src/") # normalize paths across platforms
-    str = re.sub("finished in \\d+\\.\\d\\ds", "finished in $TIME", str) # the time keeps changing, obviously
+    str = re.sub("\\b\\d+\\.\\d+s\\b", "$TIME", str) # the time keeps changing, obviously
     return str
 
 def check_output(actual, path, name):
diff --git a/src/tools/miri/test-cargo-miri/test.default.stdout.ref b/src/tools/miri/test-cargo-miri/test.default.stdout.ref
index 2d74d82f769..ef092ef703b 100644
--- a/src/tools/miri/test-cargo-miri/test.default.stdout.ref
+++ b/src/tools/miri/test-cargo-miri/test.default.stdout.ref
@@ -14,3 +14,4 @@ running 5 tests
 .....
 test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/src/tools/miri/test-cargo-miri/test.filter.stdout.ref b/src/tools/miri/test-cargo-miri/test.filter.stdout.ref
index b68bc983276..071aa5691c1 100644
--- a/src/tools/miri/test-cargo-miri/test.filter.stdout.ref
+++ b/src/tools/miri/test-cargo-miri/test.filter.stdout.ref
@@ -15,3 +15,4 @@ running 0 tests
 
 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/src/tools/miri/test-cargo-miri/test.multiple_targets.stdout.ref b/src/tools/miri/test-cargo-miri/test.multiple_targets.stdout.ref
index a376530a8cf..20139e9ffe6 100644
--- a/src/tools/miri/test-cargo-miri/test.multiple_targets.stdout.ref
+++ b/src/tools/miri/test-cargo-miri/test.multiple_targets.stdout.ref
@@ -25,8 +25,10 @@ running 5 tests
 .....
 test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
 
 running 5 tests
 .....
 test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr b/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr
index 17efb10ceb0..449a29088a0 100644
--- a/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr
+++ b/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr
@@ -5,6 +5,7 @@ LL |             *x = 1;
    |             ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/async-shared-mutable.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.tree.stderr b/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.tree.stderr
index 9e992f88f0c..95b7e99dedc 100644
--- a/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.tree.stderr
@@ -5,6 +5,7 @@ LL |     let _val = *target_alias;
    |                ^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child read access
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/alias_through_mutation.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr
index e331ff2406d..207ed3131af 100644
--- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr
@@ -5,6 +5,7 @@ LL |     *x = 1;
    |     ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr
index a2191da0b4f..90b1b1294c7 100644
--- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr
@@ -5,6 +5,7 @@ LL |     *y = 2;
    |     ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr
index e92cad1777b..73a50276463 100644
--- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr
@@ -5,6 +5,7 @@ LL |     *x = 1;
    |     ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr
index e195b0a7e87..a6a6da3fa2a 100644
--- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr
@@ -5,6 +5,7 @@ LL |         crate::intrinsics::write_via_move(dest, src);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (currently Frozen) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.tree.stderr
index 30832ae60ac..d4cfab024ba 100644
--- a/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.tree.stderr
@@ -5,6 +5,7 @@ LL |         *LEAK = 7;
    |         ^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/box_exclusive_violation1.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.tree.stderr b/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.tree.stderr
index d7b865d57a8..3c8ec7f7d3e 100644
--- a/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.tree.stderr
@@ -5,6 +5,7 @@ LL |     *y
    |     ^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign read access would cause the protected tag <TAG> (currently Active) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.tree.stderr b/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.tree.stderr
index ad5df3399c8..6588bc25df1 100644
--- a/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.tree.stderr
@@ -5,6 +5,7 @@ LL |     v2[1] = 7;
    |     ^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/both_borrows/buggy_as_mut_slice.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.tree.stderr b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.tree.stderr
index 4ab49e75adb..6ef27515fcd 100644
--- a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.tree.stderr
@@ -5,6 +5,7 @@ LL |     b[1] = 6;
    |     ^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/both_borrows/buggy_split_at_mut.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write1.tree.stderr
index b718e19ff5a..4ffc7a75e66 100644
--- a/src/tools/miri/tests/fail/both_borrows/illegal_write1.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_write1.tree.stderr
@@ -5,6 +5,7 @@ LL |         unsafe { *x = 42 };
    |                  ^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/illegal_write1.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write5.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write5.tree.stderr
index d4c78c4bd33..fc5b12da70e 100644
--- a/src/tools/miri/tests/fail/both_borrows/illegal_write5.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_write5.tree.stderr
@@ -5,6 +5,7 @@ LL |     let _val = *xref;
    |                ^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child read access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/both_borrows/illegal_write5.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr
index e537bc18c05..6780e52c3ba 100644
--- a/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { *y = 2 };
    |              ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (currently Active) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.tree.stderr
index af90e75d384..90a89e48e61 100644
--- a/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { *x = 0 };
    |              ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (currently Frozen) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.tree.stderr b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.tree.stderr
index 5d5f5e59ead..8bac71dcd46 100644
--- a/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { *x = 0 };
    |              ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (currently Frozen) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.tree.stderr b/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.tree.stderr
index 86c12603c07..e4bde2f7aea 100644
--- a/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.tree.stderr
@@ -5,6 +5,7 @@ LL |     let _val = *xref_in_mem;
    |                ^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/load_invalid_shr.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.tree.stderr
index 5f14f23e8d6..f5c1dea69f0 100644
--- a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.tree.stderr
@@ -5,6 +5,7 @@ LL |         *LEAK = 7;
    |         ^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/mut_exclusive_violation1.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.tree.stderr
index edc72b0abbf..c1b0821a9fe 100644
--- a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.tree.stderr
@@ -5,6 +5,7 @@ LL |         *raw1 = 3;
    |         ^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/both_borrows/mut_exclusive_violation2.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr
index 1984a6a0746..aa07ef53b31 100644
--- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr
@@ -5,6 +5,7 @@ LL |                 self.1.deallocate(From::from(ptr.cast()), layout);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> (currently Reserved (conflicted)) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr
index 7bd0cd14d79..c8a72c59176 100644
--- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr
@@ -5,6 +5,7 @@ LL |                 self.1.deallocate(From::from(ptr.cast()), layout);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> (currently Reserved (conflicted)) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/both_borrows/outdated_local.tree.stderr b/src/tools/miri/tests/fail/both_borrows/outdated_local.tree.stderr
index 8cb44d2635f..5310f8b9d09 100644
--- a/src/tools/miri/tests/fail/both_borrows/outdated_local.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/outdated_local.tree.stderr
@@ -5,6 +5,7 @@ LL |     assert_eq!(unsafe { *y }, 1);
    |                         ^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child read access
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/outdated_local.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.tree.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.tree.stderr
index 1bd760426da..3c813d35031 100644
--- a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.tree.stderr
@@ -5,6 +5,7 @@ LL |     foo(xref);
    |         ^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/pass_invalid_shr.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.tree.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.tree.stderr
index 2f999a8aae0..4747c65ba83 100644
--- a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.tree.stderr
@@ -5,6 +5,7 @@ LL |     foo(some_xref);
    |         ^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/pass_invalid_shr_option.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.tree.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.tree.stderr
index b30bda598d2..da011cfd3f7 100644
--- a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.tree.stderr
@@ -5,6 +5,7 @@ LL |     foo(pair_xref);
    |         ^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/pass_invalid_shr_tuple.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.tree.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.tree.stderr
index 2b2650a254d..ee0f313d980 100644
--- a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.tree.stderr
@@ -5,6 +5,7 @@ LL |     ret
    |     ^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/return_invalid_shr.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.tree.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.tree.stderr
index b8e963f87d9..16110e5b062 100644
--- a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.tree.stderr
@@ -5,6 +5,7 @@ LL |     ret
    |     ^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/return_invalid_shr_option.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.tree.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.tree.stderr
index 8e499369f08..f93698f570e 100644
--- a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.tree.stderr
@@ -5,6 +5,7 @@ LL |     ret
    |     ^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/return_invalid_shr_tuple.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.tree.stderr
index a28754f5412..d9b75f65f75 100644
--- a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.tree.stderr
@@ -5,6 +5,7 @@ LL |         *(x as *const i32 as *mut i32) = 7;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/shr_frozen_violation1.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.tree.stderr
index 0eb2dc3df77..c680c04137b 100644
--- a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.tree.stderr
@@ -5,6 +5,7 @@ LL |         let _val = *frozen;
    |                    ^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child read access
 help: the accessed tag <TAG> was created here, in the initial state Frozen
   --> tests/fail/both_borrows/shr_frozen_violation2.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/zero-sized-protected.tree.stderr b/src/tools/miri/tests/fail/both_borrows/zero-sized-protected.tree.stderr
index 2a9a9afb0f0..66a7d7794e9 100644
--- a/src/tools/miri/tests/fail/both_borrows/zero-sized-protected.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/zero-sized-protected.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { dealloc(ptr, l) };
    |              ^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the allocation of the accessed tag <TAG> (root of the allocation) also contains the strongly protected tag <TAG>
    = help: the strongly protected tag <TAG> disallows deallocations
 help: the accessed tag <TAG> was created here
diff --git a/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs b/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs
index 2b861e5447b..7147813c4b6 100644
--- a/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs
+++ b/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs
@@ -1,3 +1,5 @@
+#![allow(integer_to_ptr_transmutes)]
+
 use std::mem::transmute;
 
 #[cfg(target_pointer_width = "32")]
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs
new file mode 100644
index 00000000000..744d64b9b1e
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs
@@ -0,0 +1,36 @@
+//! Ensure we detect aliasing of two in-place arguments for the tricky case where they do not
+//! live in memory.
+//@revisions: stack tree
+//@[tree]compile-flags: -Zmiri-tree-borrows
+// Validation forces more things into memory, which we can't have here.
+//@compile-flags: -Zmiri-disable-validation
+#![feature(custom_mir, core_intrinsics)]
+use std::intrinsics::mir::*;
+
+pub struct S(i32);
+
+#[custom_mir(dialect = "runtime", phase = "optimized")]
+fn main() {
+    mir! {
+        let _unit: ();
+        {
+            let staging = S(42); // This forces `staging` into memory...
+            let non_copy = staging; // ... so we move it to a non-inmemory local here.
+            // This specifically uses a type with scalar representation to tempt Miri to use the
+            // efficient way of storing local variables (outside adressable memory).
+            Call(_unit = callee(Move(non_copy), Move(non_copy)), ReturnTo(after_call), UnwindContinue())
+            //~[stack]^ ERROR: not granting access
+            //~[tree]| ERROR: /read access .* forbidden/
+        }
+        after_call = {
+            Return()
+        }
+    }
+}
+
+pub fn callee(x: S, mut y: S) {
+    // With the setup above, if `x` and `y` are both moved,
+    // then writing to `y` will change the value stored in `x`!
+    y.0 = 0;
+    assert_eq!(x.0, 42);
+}
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.stack.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.stack.stderr
new file mode 100644
index 00000000000..0c1100cae63
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.stack.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
+  --> tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+   |
+LL |             Call(_unit = callee(Move(non_copy), Move(non_copy)), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created here, as the root tag for ALLOC
+  --> tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+   |
+LL |             Call(_unit = callee(Move(non_copy), Move(non_copy)), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> is this argument
+  --> tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+   |
+LL |     y.0 = 0;
+   |     ^^^^^^^
+   = note: BACKTRACE (of the first span):
+   = note: inside `main` at tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.tree.stderr
new file mode 100644
index 00000000000..2266a9c39f9
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.tree.stderr
@@ -0,0 +1,34 @@
+error: Undefined Behavior: read access through <TAG> (root of the allocation) at ALLOC[0x0] is forbidden
+  --> tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+   |
+LL |             Call(_unit = callee(Move(non_copy), Move(non_copy)), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
+   = help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
+   = help: this foreign read access would cause the protected tag <TAG> (currently Active) to become Disabled
+   = help: protected tags must never be Disabled
+help: the accessed tag <TAG> was created here
+  --> tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+   |
+LL |             Call(_unit = callee(Move(non_copy), Move(non_copy)), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: the protected tag <TAG> was created here, in the initial state Reserved
+  --> tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+   |
+LL |     y.0 = 0;
+   |     ^^^^^^^
+help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
+  --> tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+   |
+LL |     y.0 = 0;
+   |     ^^^^^^^
+   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
+   = note: BACKTRACE (of the first span):
+   = note: inside `main` at tests/fail/function_calls/arg_inplace_locals_alias.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs
new file mode 100644
index 00000000000..dff724f8d96
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs
@@ -0,0 +1,34 @@
+//! Ensure we detect aliasing of a in-place argument with the return place for the tricky case where
+//! they do not live in memory.
+//@revisions: stack tree
+//@[tree]compile-flags: -Zmiri-tree-borrows
+// Validation forces more things into memory, which we can't have here.
+//@compile-flags: -Zmiri-disable-validation
+#![feature(custom_mir, core_intrinsics)]
+use std::intrinsics::mir::*;
+
+#[allow(unused)]
+pub struct S(i32);
+
+#[custom_mir(dialect = "runtime", phase = "optimized")]
+fn main() {
+    mir! {
+        let _unit: ();
+        {
+            let staging = S(42); // This forces `staging` into memory...
+            let _non_copy = staging; // ... so we move it to a non-inmemory local here.
+            // This specifically uses a type with scalar representation to tempt Miri to use the
+            // efficient way of storing local variables (outside adressable memory).
+            Call(_non_copy = callee(Move(_non_copy)), ReturnTo(after_call), UnwindContinue())
+            //~[stack]^ ERROR: not granting access
+            //~[tree]| ERROR: /reborrow .* forbidden/
+        }
+        after_call = {
+            Return()
+        }
+    }
+}
+
+pub fn callee(x: S) -> S {
+    x
+}
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.stack.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.stack.stderr
new file mode 100644
index 00000000000..fcd5b8752e7
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.stack.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
+  --> tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+   |
+LL |             Call(_non_copy = callee(Move(_non_copy)), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created here, as the root tag for ALLOC
+  --> tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+   |
+LL |             Call(_non_copy = callee(Move(_non_copy)), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> is this argument
+  --> tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+   |
+LL |     x
+   |     ^
+   = note: BACKTRACE (of the first span):
+   = note: inside `main` at tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.tree.stderr
new file mode 100644
index 00000000000..b7f514de0af
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.tree.stderr
@@ -0,0 +1,34 @@
+error: Undefined Behavior: reborrow through <TAG> (root of the allocation) at ALLOC[0x0] is forbidden
+  --> tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+   |
+LL |             Call(_non_copy = callee(Move(_non_copy)), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
+   = help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
+   = help: this reborrow (acting as a foreign read access) would cause the protected tag <TAG> (currently Active) to become Disabled
+   = help: protected tags must never be Disabled
+help: the accessed tag <TAG> was created here
+  --> tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+   |
+LL |             Call(_non_copy = callee(Move(_non_copy)), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: the protected tag <TAG> was created here, in the initial state Reserved
+  --> tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+   |
+LL |     x
+   |     ^
+help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
+  --> tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+   |
+LL |     x
+   |     ^
+   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
+   = note: BACKTRACE (of the first span):
+   = note: inside `main` at tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
index 2f1dc1988f5..1995528e9f9 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { ptr.write(S(0)) };
    |              ^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (currently Active) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
index 5db5a6cac4d..e506a61c6bb 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { ptr.read() };
    |              ^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign read access would cause the protected tag <TAG> (currently Active) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.none.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.none.stderr
index 24091547258..d478568ceae 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.none.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.none.stderr
@@ -11,8 +11,8 @@ LL |     unsafe { ptr.read() };
 note: inside `main`
   --> tests/fail/function_calls/return_pointer_aliasing_read.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Uninitialized memory occurred at ALLOC[0x0..0x4], in this allocation:
 ALLOC (stack variable, size: 4, align: 4) {
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs
index a6e0134bd17..dc22e129e18 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs
@@ -10,11 +10,11 @@ use std::intrinsics::mir::*;
 pub fn main() {
     mir! {
         {
-            let x = 0;
-            let ptr = &raw mut x;
+            let _x = 0;
+            let ptr = &raw mut _x;
             // We arrange for `myfun` to have a pointer that aliases
             // its return place. Even just reading from that pointer is UB.
-            Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+            Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
         }
 
         after_call = {
@@ -25,7 +25,7 @@ pub fn main() {
 
 fn myfun(ptr: *mut i32) -> i32 {
     unsafe { ptr.read() };
-    //~[stack]^ ERROR: not granting access
+    //~[stack]^ ERROR: does not exist in the borrow stack
     //~[tree]| ERROR: /read access .* forbidden/
     //~[none]| ERROR: uninitialized
     // Without an aliasing model, reads are "fine" but at least they return uninit data.
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.stack.stderr
index 77cc0332a1c..86adbab353b 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.stack.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.stack.stderr
@@ -1,8 +1,8 @@
-error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
   --> tests/fail/function_calls/return_pointer_aliasing_read.rs:LL:CC
    |
 LL |     unsafe { ptr.read() };
-   |              ^^^^^^^^^^ Undefined Behavior occurred here
+   |              ^^^^^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
@@ -11,12 +11,12 @@ help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
    |
 LL | /     mir! {
 LL | |         {
-LL | |             let x = 0;
-LL | |             let ptr = &raw mut x;
+LL | |             let _x = 0;
+LL | |             let ptr = &raw mut _x;
 ...  |
 LL | |     }
    | |_____^
-help: <TAG> is this argument
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique in-place function argument/return passing protection
   --> tests/fail/function_calls/return_pointer_aliasing_read.rs:LL:CC
    |
 LL |     unsafe { ptr.read() };
@@ -26,8 +26,8 @@ LL |     unsafe { ptr.read() };
 note: inside `main`
   --> tests/fail/function_calls/return_pointer_aliasing_read.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.tree.stderr
index bae3819c979..b1aa2ba2886 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { ptr.read() };
    |              ^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign read access would cause the protected tag <TAG> (currently Active) to become Disabled
    = help: protected tags must never be Disabled
@@ -13,8 +14,8 @@ help: the accessed tag <TAG> was created here
    |
 LL | /     mir! {
 LL | |         {
-LL | |             let x = 0;
-LL | |             let ptr = &raw mut x;
+LL | |             let _x = 0;
+LL | |             let ptr = &raw mut _x;
 ...  |
 LL | |     }
    | |_____^
@@ -34,8 +35,8 @@ LL |     unsafe { ptr.read() };
 note: inside `main`
   --> tests/fail/function_calls/return_pointer_aliasing_read.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs
index 6155e925c4b..2fddaf37235 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs
@@ -14,7 +14,7 @@ pub fn main() {
             let ptr = &raw mut _x;
             // We arrange for `myfun` to have a pointer that aliases
             // its return place. Writing to that pointer is UB.
-            Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+            Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
         }
 
         after_call = {
@@ -26,7 +26,7 @@ pub fn main() {
 fn myfun(ptr: *mut i32) -> i32 {
     // This overwrites the return place, which shouldn't be possible through another pointer.
     unsafe { ptr.write(0) };
-    //~[stack]^ ERROR: strongly protected
+    //~[stack]^ ERROR: does not exist in the borrow stack
     //~[tree]| ERROR: /write access .* forbidden/
     13
 }
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.stack.stderr
index 828b2339f8c..faae6172d75 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.stack.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.stack.stderr
@@ -1,8 +1,8 @@
-error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
   --> tests/fail/function_calls/return_pointer_aliasing_write.rs:LL:CC
    |
 LL |     unsafe { ptr.write(0) };
-   |              ^^^^^^^^^^^^ Undefined Behavior occurred here
+   |              ^^^^^^^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
@@ -16,7 +16,7 @@ LL | |             let ptr = &raw mut _x;
 ...  |
 LL | |     }
    | |_____^
-help: <TAG> is this argument
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique in-place function argument/return passing protection
   --> tests/fail/function_calls/return_pointer_aliasing_write.rs:LL:CC
    |
 LL |     unsafe { ptr.write(0) };
@@ -26,8 +26,8 @@ LL |     unsafe { ptr.write(0) };
 note: inside `main`
   --> tests/fail/function_calls/return_pointer_aliasing_write.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.tree.stderr
index e0df9370fee..0cf449ea3ec 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { ptr.write(0) };
    |              ^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (currently Active) to become Disabled
    = help: protected tags must never be Disabled
@@ -34,8 +35,8 @@ LL |     unsafe { ptr.write(0) };
 note: inside `main`
   --> tests/fail/function_calls/return_pointer_aliasing_write.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs
index 37ee7ae1b0d..5f3ecb65022 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs
@@ -16,7 +16,7 @@ pub fn main() {
             let ptr = &raw mut _x;
             // We arrange for `myfun` to have a pointer that aliases
             // its return place. Writing to that pointer is UB.
-            Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+            Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
         }
 
         after_call = {
@@ -32,7 +32,7 @@ fn myfun(ptr: *mut i32) -> i32 {
 fn myfun2(ptr: *mut i32) -> i32 {
     // This overwrites the return place, which shouldn't be possible through another pointer.
     unsafe { ptr.write(0) };
-    //~[stack]^ ERROR: strongly protected
+    //~[stack]^ ERROR: does not exist in the borrow stack
     //~[tree]| ERROR: /write access .* forbidden/
     13
 }
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr
index f5183cfaf5b..1a18857bb17 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.stack.stderr
@@ -1,8 +1,8 @@
-error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
   --> tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs:LL:CC
    |
 LL |     unsafe { ptr.write(0) };
-   |              ^^^^^^^^^^^^ Undefined Behavior occurred here
+   |              ^^^^^^^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
@@ -16,18 +16,18 @@ LL | |             let ptr = &raw mut _x;
 ...  |
 LL | |     }
    | |_____^
-help: <TAG> is this argument
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique in-place function argument/return passing protection
   --> tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs:LL:CC
    |
-LL |     unsafe { ptr.write(0) };
-   |     ^^^^^^^^^^^^^^^^^^^^^^^
+LL |     become myfun2(ptr)
+   |     ^^^^^^^^^^^^^^^^^^
    = note: BACKTRACE (of the first span):
    = note: inside `myfun2` at tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs:LL:CC
 note: inside `main`
   --> tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr
index fa03ed6869b..a006c6feae4 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.tree.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { ptr.write(0) };
    |              ^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (currently Active) to become Disabled
    = help: protected tags must never be Disabled
@@ -34,8 +35,8 @@ LL |     unsafe { ptr.write(0) };
 note: inside `main`
   --> tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs:LL:CC
    |
-LL |             Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
diff --git a/src/tools/miri/tests/fail/provenance/provenance_transmute.rs b/src/tools/miri/tests/fail/provenance/provenance_transmute.rs
index d72f10530d7..60cb9a7f6bf 100644
--- a/src/tools/miri/tests/fail/provenance/provenance_transmute.rs
+++ b/src/tools/miri/tests/fail/provenance/provenance_transmute.rs
@@ -1,5 +1,7 @@
 //@compile-flags: -Zmiri-permissive-provenance
 
+#![allow(integer_to_ptr_transmutes)]
+
 use std::mem;
 
 // This is the example from
diff --git a/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.stderr b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.stderr
index 63c6287f20d..9e955a6d5b1 100644
--- a/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.stderr
@@ -5,6 +5,7 @@ LL |     *y += 1; // Failure
    |     ^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/alternate-read-write.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.stderr b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.stderr
index 81d5aba6b2e..5e43d174ed9 100644
--- a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.stderr
@@ -11,6 +11,7 @@ LL |         (*a).field1 = 88;
    |         ^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (a) has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Cell
   --> tests/fail/tree_borrows/cell-inside-struct.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/error-range.stderr b/src/tools/miri/tests/fail/tree_borrows/error-range.stderr
index 9a52f68d9b6..3dc120a20d2 100644
--- a/src/tools/miri/tests/fail/tree_borrows/error-range.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/error-range.stderr
@@ -5,6 +5,7 @@ LL |         rmut[5] += 1;
    |         ^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child read access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/error-range.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr b/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr
index ed345aae38f..7886029dccf 100644
--- a/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr
@@ -5,6 +5,7 @@ LL |         *z = 2;
    |         ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/fnentry_invalidation.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/outside-range.stderr b/src/tools/miri/tests/fail/tree_borrows/outside-range.stderr
index bbd33dc3560..7960f42faa5 100644
--- a/src/tools/miri/tests/fail/tree_borrows/outside-range.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/outside-range.stderr
@@ -5,6 +5,7 @@ LL |     *y.add(3) = 42;
    |     ^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (currently Reserved) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.stderr b/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.stderr
index a4119bc0178..2edbbd80569 100644
--- a/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.stderr
@@ -5,6 +5,7 @@ LL |         *ptr = 0;
    |         ^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/parent_read_freezes_raw_mut.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr
index de01a9f0e80..c00c67173b7 100644
--- a/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr
@@ -5,6 +5,7 @@ LL |     *nope = 31;
    |     ^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is a child of the conflicting tag <TAG>
    = help: the conflicting tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here
diff --git a/src/tools/miri/tests/fail/tree_borrows/protector-write-lazy.stderr b/src/tools/miri/tests/fail/tree_borrows/protector-write-lazy.stderr
index af226b9e3be..7cebd7f32a0 100644
--- a/src/tools/miri/tests/fail/tree_borrows/protector-write-lazy.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/protector-write-lazy.stderr
@@ -5,6 +5,7 @@ LL |     unsafe { println!("Value of funky: {}", *funky_ptr_lazy_on_fst_elem) }
    |                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/protector-write-lazy.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.stderr b/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.stderr
index 8421df5ab79..012d7caef50 100644
--- a/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.stderr
@@ -5,6 +5,7 @@ LL |     *(x as *mut u8).byte_sub(1) = 42;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr
index 00d5c3e6b67..b1c3ffe86ef 100644
--- a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr
@@ -15,6 +15,7 @@ LL |             *y = 1;
    |             ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (y, callee:y, caller:y) is foreign to the protected tag <TAG> (callee:x) (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (callee:x) (currently Reserved) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr
index c003938db33..5f3129b9dc0 100644
--- a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr
@@ -15,6 +15,7 @@ LL |             *y = 0;
    |             ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> (y, callee:y, caller:y) is foreign to the protected tag <TAG> (callee:x) (i.e., it is not a child)
    = help: this foreign write access would cause the protected tag <TAG> (callee:x) (currently Reserved) to become Disabled
    = help: protected tags must never be Disabled
diff --git a/src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.with.stderr b/src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.with.stderr
index a8db7070333..7565fa6203f 100644
--- a/src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.with.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.with.stderr
@@ -19,6 +19,7 @@ LL |         unsafe { *y = 13 }
    |                  ^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/reservedim_spurious_write.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.without.stderr b/src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.without.stderr
index 5eeefd450c6..7f791c88dde 100644
--- a/src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.without.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/reservedim_spurious_write.without.stderr
@@ -19,6 +19,7 @@ LL |         unsafe { *y = 13 }
    |                  ^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/reservedim_spurious_write.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.stderr b/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.stderr
index a0ebf0ecbd2..ba8ab472872 100644
--- a/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.stderr
@@ -5,6 +5,7 @@ LL |     *ret = 3;
    |     ^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is a child of the conflicting tag <TAG>
    = help: the conflicting tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here
diff --git a/src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr b/src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr
index a3b0d5f13ad..8f2534d6b6e 100644
--- a/src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr
@@ -18,6 +18,7 @@ LL |                 *y = 2;
    |                 ^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/spurious_read.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr
index 56617f6d6a6..685abee3292 100644
--- a/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr
@@ -5,6 +5,7 @@ LL |                 self.1.deallocate(From::from(ptr.cast()), layout);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the allocation of the accessed tag <TAG> also contains the strongly protected tag <TAG>
    = help: the strongly protected tag <TAG> disallows deallocations
 help: the accessed tag <TAG> was created here
diff --git a/src/tools/miri/tests/fail/tree_borrows/subtree_traversal_skipping_diagnostics.stderr b/src/tools/miri/tests/fail/tree_borrows/subtree_traversal_skipping_diagnostics.stderr
index 8669a14ecc7..b98d2fafcf4 100644
--- a/src/tools/miri/tests/fail/tree_borrows/subtree_traversal_skipping_diagnostics.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/subtree_traversal_skipping_diagnostics.stderr
@@ -5,6 +5,7 @@ LL |     *m = 42;
    |     ^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> is a child of the conflicting tag <TAG>
    = help: the conflicting tag <TAG> has state Frozen which forbids this child write access
 help: the accessed tag <TAG> was created here
diff --git a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr
index 693d1853550..7f55e06a6bb 100644
--- a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr
@@ -5,6 +5,7 @@ LL |     fn add(&mut self, n: u64) -> u64 {
    |            ^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
    = help: the accessed tag <TAG> has state Disabled which forbids this reborrow (acting as a child read access)
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/write-during-2phase.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/validity/dangling_ref1.rs b/src/tools/miri/tests/fail/validity/dangling_ref1.rs
index fc3a9f34463..57ba1117e76 100644
--- a/src/tools/miri/tests/fail/validity/dangling_ref1.rs
+++ b/src/tools/miri/tests/fail/validity/dangling_ref1.rs
@@ -1,5 +1,8 @@
 // Make sure we catch this even without Stacked Borrows
 //@compile-flags: -Zmiri-disable-stacked-borrows
+
+#![allow(integer_to_ptr_transmutes)]
+
 use std::mem;
 
 fn main() {
diff --git a/src/tools/miri/tests/panic/oob_subslice.stderr b/src/tools/miri/tests/panic/oob_subslice.stderr
index f8270f4ad4d..e1e5bd33d31 100644
--- a/src/tools/miri/tests/panic/oob_subslice.stderr
+++ b/src/tools/miri/tests/panic/oob_subslice.stderr
@@ -1,5 +1,5 @@
 
 thread 'main' ($TID) panicked at tests/panic/oob_subslice.rs:LL:CC:
-range end index 5 out of range for slice of length 4
+range end index 4 out of range for slice of length 4
 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
 note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect
diff --git a/src/tools/miri/tests/panic/transmute_fat2.rs b/src/tools/miri/tests/panic/transmute_fat2.rs
index e695ff2d57b..7441f25d03e 100644
--- a/src/tools/miri/tests/panic/transmute_fat2.rs
+++ b/src/tools/miri/tests/panic/transmute_fat2.rs
@@ -1,3 +1,5 @@
+#![allow(integer_to_ptr_transmutes)]
+
 fn main() {
     #[cfg(all(target_endian = "little", target_pointer_width = "64"))]
     let bad = unsafe { std::mem::transmute::<u128, &[u8]>(42) };
diff --git a/src/tools/miri/tests/pass/binops.rs b/src/tools/miri/tests/pass/binops.rs
index 0aff7acb29d..fcbe6c85b7b 100644
--- a/src/tools/miri/tests/pass/binops.rs
+++ b/src/tools/miri/tests/pass/binops.rs
@@ -32,6 +32,7 @@ fn test_bool() {
     assert_eq!(true ^ true, false);
 }
 
+#[allow(integer_to_ptr_transmutes)]
 fn test_ptr() {
     unsafe {
         let p1: *const u8 = ::std::mem::transmute(0_usize);
diff --git a/src/tools/miri/tests/pass/function_calls/exported_symbol_weak.rs b/src/tools/miri/tests/pass/function_calls/exported_symbol_weak.rs
new file mode 100644
index 00000000000..abf4b6718ab
--- /dev/null
+++ b/src/tools/miri/tests/pass/function_calls/exported_symbol_weak.rs
@@ -0,0 +1,39 @@
+#![feature(linkage)]
+
+// FIXME move this module to a separate crate once aux-build is allowed
+// This currently depends on the fact that miri skips the codegen check
+// that denies multiple symbols with the same name.
+mod first {
+    #[no_mangle]
+    #[linkage = "weak"]
+    extern "C" fn foo() -> i32 {
+        1
+    }
+
+    #[no_mangle]
+    #[linkage = "weak"]
+    extern "C" fn bar() -> i32 {
+        2
+    }
+}
+
+mod second {
+    #[no_mangle]
+    extern "C" fn bar() -> i32 {
+        3
+    }
+}
+
+extern "C" {
+    fn foo() -> i32;
+    fn bar() -> i32;
+}
+
+fn main() {
+    unsafe {
+        // If there is no non-weak definition, the weak definition will be used.
+        assert_eq!(foo(), 1);
+        // Non-weak definition takes presedence.
+        assert_eq!(bar(), 3);
+    }
+}
diff --git a/src/tools/miri/tests/pass/prefetch.rs b/src/tools/miri/tests/pass/prefetch.rs
new file mode 100644
index 00000000000..99c75c38bde
--- /dev/null
+++ b/src/tools/miri/tests/pass/prefetch.rs
@@ -0,0 +1,23 @@
+#![feature(core_intrinsics)]
+
+// Test that these intrinsics work. Their behavior should be a no-op.
+
+fn main() {
+    static X: [u8; 8] = [0; 8];
+
+    ::std::intrinsics::prefetch_read_data::<_, 1>(::std::ptr::null::<u8>());
+    ::std::intrinsics::prefetch_read_data::<_, 2>(::std::ptr::dangling::<u8>());
+    ::std::intrinsics::prefetch_read_data::<_, 3>(X.as_ptr());
+
+    ::std::intrinsics::prefetch_write_data::<_, 1>(::std::ptr::null::<u8>());
+    ::std::intrinsics::prefetch_write_data::<_, 2>(::std::ptr::dangling::<u8>());
+    ::std::intrinsics::prefetch_write_data::<_, 3>(X.as_ptr());
+
+    ::std::intrinsics::prefetch_read_instruction::<_, 1>(::std::ptr::null::<u8>());
+    ::std::intrinsics::prefetch_read_instruction::<_, 2>(::std::ptr::dangling::<u8>());
+    ::std::intrinsics::prefetch_read_instruction::<_, 3>(X.as_ptr());
+
+    ::std::intrinsics::prefetch_write_instruction::<_, 1>(::std::ptr::null::<u8>());
+    ::std::intrinsics::prefetch_write_instruction::<_, 2>(::std::ptr::dangling::<u8>());
+    ::std::intrinsics::prefetch_write_instruction::<_, 3>(X.as_ptr());
+}
diff --git a/src/tools/miri/tests/pass/too-large-primval-write-problem.rs b/src/tools/miri/tests/pass/too-large-primval-write-problem.rs
index f4c418bd78a..00882b7ecca 100644
--- a/src/tools/miri/tests/pass/too-large-primval-write-problem.rs
+++ b/src/tools/miri/tests/pass/too-large-primval-write-problem.rs
@@ -7,6 +7,8 @@
 //
 // This is just intended as a regression test to make sure we don't reintroduce this problem.
 
+#![allow(integer_to_ptr_transmutes)]
+
 #[cfg(target_pointer_width = "32")]
 fn main() {
     use std::mem::transmute;
diff --git a/src/tools/miri/triagebot.toml b/src/tools/miri/triagebot.toml
index a0ce9f80024..c747cbb0a52 100644
--- a/src/tools/miri/triagebot.toml
+++ b/src/tools/miri/triagebot.toml
@@ -50,11 +50,14 @@ new_pr = true
 [autolabel."S-waiting-on-author"]
 new_draft = true
 
-# Automatically close and reopen PRs made by bots to run CI on them
-[bot-pull-requests]
-
-# Canonicalize issue numbers to avoid closing the wrong issue when upstreaming this subtree
+# Canonicalize issue numbers to avoid closing the wrong issue when upstreaming this subtree.
 [canonicalize-issue-links]
 
-# Prevents mentions in commits to avoid users being spammed
+# Prevents mentions in commits to avoid users being spammed.
 [no-mentions]
+
+# Show range-diff links on force pushes.
+[range-diff]
+
+# Add link to the review body to review changes since posting it.
+[review-changes-since]
diff --git a/src/tools/nix-dev-shell/shell.nix b/src/tools/nix-dev-shell/shell.nix
index 0adbacf7e8d..ad33b121f97 100644
--- a/src/tools/nix-dev-shell/shell.nix
+++ b/src/tools/nix-dev-shell/shell.nix
@@ -14,6 +14,7 @@ pkgs.mkShell {
   packages = [
     pkgs.git
     pkgs.nix
+    pkgs.glibc.static
     x
     # Get the runtime deps of the x wrapper
   ] ++ lists.flatten (attrsets.attrValues env);
diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs
index a8d4c93d160..a3935f98359 100644
--- a/src/tools/opt-dist/src/exec.rs
+++ b/src/tools/opt-dist/src/exec.rs
@@ -189,6 +189,12 @@ impl Bootstrap {
         self
     }
 
+    /// Rebuild rustc in case of statically linked LLVM
+    pub fn rustc_rebuild(mut self) -> Self {
+        self.cmd = self.cmd.arg("--keep-stage").arg("0");
+        self
+    }
+
     pub fn run(self, timer: &mut TimerSection) -> anyhow::Result<()> {
         self.cmd.run()?;
         let metrics = load_metrics(&self.metrics_path)?;
diff --git a/src/tools/opt-dist/src/main.rs b/src/tools/opt-dist/src/main.rs
index 19706b4a4f0..339c25552ad 100644
--- a/src/tools/opt-dist/src/main.rs
+++ b/src/tools/opt-dist/src/main.rs
@@ -375,8 +375,14 @@ fn execute_pipeline(
 
     let mut dist = Bootstrap::dist(env, &dist_args)
         .llvm_pgo_optimize(llvm_pgo_profile.as_ref())
-        .rustc_pgo_optimize(&rustc_pgo_profile)
-        .avoid_rustc_rebuild();
+        .rustc_pgo_optimize(&rustc_pgo_profile);
+
+    // if LLVM is not built we'll have PGO optimized rustc
+    dist = if env.supports_shared_llvm() || !env.build_llvm() {
+        dist.avoid_rustc_rebuild()
+    } else {
+        dist.rustc_rebuild()
+    };
 
     for bolt_profile in bolt_profiles {
         dist = dist.with_bolt_profile(bolt_profile);
diff --git a/src/tools/rustbook/Cargo.lock b/src/tools/rustbook/Cargo.lock
index e42f266391e..cd7ee6fb4fe 100644
--- a/src/tools/rustbook/Cargo.lock
+++ b/src/tools/rustbook/Cargo.lock
@@ -97,9 +97,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.98"
+version = "1.0.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
 
 [[package]]
 name = "autocfg"
@@ -124,9 +124,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.9.1"
+version = "2.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
 
 [[package]]
 name = "block-buffer"
@@ -156,9 +156,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
 
 [[package]]
 name = "cc"
-version = "1.2.32"
+version = "1.2.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
+checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f"
 dependencies = [
  "shlex",
 ]
@@ -185,9 +185,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.43"
+version = "4.5.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
+checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -195,9 +195,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.43"
+version = "4.5.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
+checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
 dependencies = [
  "anstream",
  "anstyle",
@@ -208,18 +208,18 @@ dependencies = [
 
 [[package]]
 name = "clap_complete"
-version = "4.5.56"
+version = "4.5.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67e4efcbb5da11a92e8a609233aa1e8a7d91e38de0be865f016d14700d45a7fd"
+checksum = "4d9501bd3f5f09f7bbee01da9a511073ed30a80cd7a509f1214bb74eadea71ad"
 dependencies = [
  "clap",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "4.5.41"
+version = "4.5.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
+checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -559,7 +559,7 @@ dependencies = [
  "pest_derive",
  "serde",
  "serde_json",
- "thiserror 2.0.12",
+ "thiserror 2.0.15",
 ]
 
 [[package]]
@@ -1035,7 +1035,7 @@ version = "6.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0"
 dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.9.2",
  "libc",
  "once_cell",
  "onig_sys",
@@ -1104,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
 dependencies = [
  "memchr",
- "thiserror 2.0.12",
+ "thiserror 2.0.15",
  "ucd-trie",
 ]
 
@@ -1240,9 +1240,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.95"
+version = "1.0.101"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
 dependencies = [
  "unicode-ident",
 ]
@@ -1253,7 +1253,7 @@ version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993"
 dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.9.2",
  "memchr",
  "pulldown-cmark-escape 0.10.1",
  "unicase",
@@ -1265,7 +1265,7 @@ version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14"
 dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.9.2",
  "getopts",
  "memchr",
  "pulldown-cmark-escape 0.11.0",
@@ -1347,7 +1347,7 @@ version = "0.5.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
 dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.9.2",
 ]
 
 [[package]]
@@ -1385,6 +1385,7 @@ version = "0.1.0"
 dependencies = [
  "clap",
  "env_logger",
+ "libc",
  "mdbook",
  "mdbook-i18n-helpers",
  "mdbook-spec",
@@ -1397,7 +1398,7 @@ version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
 dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.9.2",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -1546,9 +1547,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
 name = "syn"
-version = "2.0.104"
+version = "2.0.106"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1612,12 +1613,12 @@ dependencies = [
 
 [[package]]
 name = "terminal_size"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
+checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
 dependencies = [
  "rustix",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
 ]
 
 [[package]]
@@ -1637,11 +1638,11 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "2.0.12"
+version = "2.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850"
 dependencies = [
- "thiserror-impl 2.0.12",
+ "thiserror-impl 2.0.15",
 ]
 
 [[package]]
@@ -1657,9 +1658,9 @@ dependencies = [
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.12"
+version = "2.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2116,7 +2117,7 @@ version = "0.39.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
 dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.9.2",
 ]
 
 [[package]]
diff --git a/src/tools/rustbook/Cargo.toml b/src/tools/rustbook/Cargo.toml
index c7c6e39f157..b34c39c225d 100644
--- a/src/tools/rustbook/Cargo.toml
+++ b/src/tools/rustbook/Cargo.toml
@@ -10,6 +10,9 @@ edition = "2021"
 [dependencies]
 clap = "4.0.32"
 env_logger = "0.11"
+# FIXME: Remove this pin once this rustix issue is resolved
+# https://github.com/bytecodealliance/rustix/issues/1496
+libc = "=0.2.174"
 mdbook-trpl = { path = "../../doc/book/packages/mdbook-trpl" }
 mdbook-i18n-helpers = "0.3.3"
 mdbook-spec = { path = "../../doc/reference/mdbook-spec" }
diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js
index 0baa179e16b..ff809d58e90 100644
--- a/src/tools/rustdoc-js/tester.js
+++ b/src/tools/rustdoc-js/tester.js
@@ -1,7 +1,8 @@
 /* global globalThis */
+
 const fs = require("fs");
 const path = require("path");
-
+const { isGeneratorObject } = require("util/types");
 
 function arrayToCode(array) {
     return array.map((value, index) => {
@@ -45,23 +46,16 @@ function shouldIgnoreField(fieldName) {
 }
 
 function valueMapper(key, testOutput) {
-    const isAlias = testOutput["is_alias"];
     let value = testOutput[key];
     // To make our life easier, if there is a "parent" type, we add it to the path.
     if (key === "path") {
-        if (testOutput["parent"] !== undefined) {
+        if (testOutput["parent"]) {
             if (value.length > 0) {
                 value += "::" + testOutput["parent"]["name"];
             } else {
                 value = testOutput["parent"]["name"];
             }
-        } else if (testOutput["is_alias"]) {
-            value = valueMapper(key, testOutput["original"]);
         }
-    } else if (isAlias && key === "alias") {
-        value = testOutput["name"];
-    } else if (isAlias && ["name"].includes(key)) {
-        value = testOutput["original"][key];
     }
     return value;
 }
@@ -237,7 +231,7 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
     const ignore_order = loadedFile.ignore_order;
     const exact_check = loadedFile.exact_check;
 
-    const results = await doSearch(query, loadedFile.FILTER_CRATE);
+    const { resultsTable } = await doSearch(query, loadedFile.FILTER_CRATE);
     const error_text = [];
 
     for (const key in expected) {
@@ -247,37 +241,38 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
         if (!Object.prototype.hasOwnProperty.call(expected, key)) {
             continue;
         }
-        if (!Object.prototype.hasOwnProperty.call(results, key)) {
+        if (!Object.prototype.hasOwnProperty.call(resultsTable, key)) {
             error_text.push("==> Unknown key \"" + key + "\"");
             break;
         }
         const entry = expected[key];
 
-        if (exact_check && entry.length !== results[key].length) {
+        if (exact_check && entry.length !== resultsTable[key].length) {
             error_text.push(queryName + "==> Expected exactly " + entry.length +
-                            " results but found " + results[key].length + " in '" + key + "'");
+                            " results but found " + resultsTable[key].length + " in '" + key + "'");
         }
 
         let prev_pos = -1;
         for (const [index, elem] of entry.entries()) {
-            const entry_pos = lookForEntry(elem, results[key]);
+            const entry_pos = lookForEntry(elem, resultsTable[key]);
             if (entry_pos === -1) {
                 error_text.push(queryName + "==> Result not found in '" + key + "': '" +
                                 JSON.stringify(elem) + "'");
                 // By default, we just compare the two first items.
                 let item_to_diff = 0;
-                if ((!ignore_order || exact_check) && index < results[key].length) {
+                if ((!ignore_order || exact_check) && index < resultsTable[key].length) {
                     item_to_diff = index;
                 }
                 error_text.push("Diff of first error:\n" +
-                    betterLookingDiff(elem, results[key][item_to_diff]));
+                    betterLookingDiff(elem, resultsTable[key][item_to_diff]));
             } else if (exact_check === true && prev_pos + 1 !== entry_pos) {
                 error_text.push(queryName + "==> Exact check failed at position " + (prev_pos + 1) +
                                 ": expected '" + JSON.stringify(elem) + "' but found '" +
-                                JSON.stringify(results[key][index]) + "'");
+                                JSON.stringify(resultsTable[key][index]) + "'");
             } else if (ignore_order === false && entry_pos < prev_pos) {
-                error_text.push(queryName + "==> '" + JSON.stringify(elem) + "' was supposed " +
-                                "to be before '" + JSON.stringify(results[key][prev_pos]) + "'");
+                error_text.push(queryName + "==> '" +
+                                JSON.stringify(elem) + "' was supposed to be before '" +
+                                JSON.stringify(resultsTable[key][prev_pos]) + "'");
             } else {
                 prev_pos = entry_pos;
             }
@@ -286,19 +281,20 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
     return error_text;
 }
 
-async function runCorrections(query, corrections, getCorrections, loadedFile) {
-    const qc = await getCorrections(query, loadedFile.FILTER_CRATE);
+async function runCorrections(query, corrections, doSearch, loadedFile) {
+    const { parsedQuery } = await doSearch(query, loadedFile.FILTER_CRATE);
+    const qc = parsedQuery.correction;
     const error_text = [];
 
     if (corrections === null) {
         if (qc !== null) {
-            error_text.push(`==> expected = null, found = ${qc}`);
+            error_text.push(`==> [correction] expected = null, found = ${qc}`);
         }
         return error_text;
     }
 
-    if (qc !== corrections.toLowerCase()) {
-        error_text.push(`==> expected = ${corrections}, found = ${qc}`);
+    if (qc.toLowerCase() !== corrections.toLowerCase()) {
+        error_text.push(`==> [correction] expected = ${corrections}, found = ${qc}`);
     }
 
     return error_text;
@@ -320,7 +316,7 @@ function checkResult(error_text, loadedFile, displaySuccess) {
     return 1;
 }
 
-async function runCheckInner(callback, loadedFile, entry, getCorrections, extra) {
+async function runCheckInner(callback, loadedFile, entry, extra, doSearch) {
     if (typeof entry.query !== "string") {
         console.log("FAILED");
         console.log("==> Missing `query` field");
@@ -338,7 +334,7 @@ async function runCheckInner(callback, loadedFile, entry, getCorrections, extra)
         error_text = await runCorrections(
             entry.query,
             entry.correction,
-            getCorrections,
+            doSearch,
             loadedFile,
         );
         if (checkResult(error_text, loadedFile, false) !== 0) {
@@ -348,16 +344,16 @@ async function runCheckInner(callback, loadedFile, entry, getCorrections, extra)
     return true;
 }
 
-async function runCheck(loadedFile, key, getCorrections, callback) {
+async function runCheck(loadedFile, key, doSearch, callback) {
     const expected = loadedFile[key];
 
     if (Array.isArray(expected)) {
         for (const entry of expected) {
-            if (!await runCheckInner(callback, loadedFile, entry, getCorrections, true)) {
+            if (!await runCheckInner(callback, loadedFile, entry, true, doSearch)) {
                 return 1;
             }
         }
-    } else if (!await runCheckInner(callback, loadedFile, expected, getCorrections, false)) {
+    } else if (!await runCheckInner(callback, loadedFile, expected, false, doSearch)) {
         return 1;
     }
     console.log("OK");
@@ -368,7 +364,7 @@ function hasCheck(content, checkName) {
     return content.startsWith(`const ${checkName}`) || content.includes(`\nconst ${checkName}`);
 }
 
-async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
+async function runChecks(testFile, doSearch, parseQuery) {
     let checkExpected = false;
     let checkParsed = false;
     let testFileContent = readFile(testFile);
@@ -397,18 +393,36 @@ async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
     let res = 0;
 
     if (checkExpected) {
-        res += await runCheck(loadedFile, "EXPECTED", getCorrections, (query, expected, text) => {
+        res += await runCheck(loadedFile, "EXPECTED", doSearch, (query, expected, text) => {
             return runSearch(query, expected, doSearch, loadedFile, text);
         });
     }
     if (checkParsed) {
-        res += await runCheck(loadedFile, "PARSED", getCorrections, (query, expected, text) => {
+        res += await runCheck(loadedFile, "PARSED", doSearch, (query, expected, text) => {
             return runParser(query, expected, parseQuery, text);
         });
     }
     return res;
 }
 
+function mostRecentMatch(staticFiles, regex) {
+    const matchingEntries = fs.readdirSync(staticFiles)
+        .filter(f => f.match(regex))
+        .map(f => {
+            const stats = fs.statSync(path.join(staticFiles, f));
+            return {
+                path: f,
+                time: stats.mtimeMs,
+            };
+        });
+    if (matchingEntries.length === 0) {
+        throw "No static file matching regex";
+    }
+    // We sort entries in descending order.
+    matchingEntries.sort((a, b) => b.time - a.time);
+    return matchingEntries[0].path;
+}
+
 /**
  * Load searchNNN.js and search-indexNNN.js.
  *
@@ -416,71 +430,89 @@ async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
  * @param {string} resource_suffix - Version number between filename and .js, e.g. "1.59.0"
  * @returns {Object}               - Object containing keys: `doSearch`, which runs a search
  *   with the loaded index and returns a table of results; `parseQuery`, which is the
- *   `parseQuery` function exported from the search module; and `getCorrections`, which runs
+ *   `parseQuery` function exported from the search module, which runs
  *   a search but returns type name corrections instead of results.
  */
-function loadSearchJS(doc_folder, resource_suffix) {
-    const searchIndexJs = path.join(doc_folder, "search-index" + resource_suffix + ".js");
-    const searchIndex = require(searchIndexJs);
-
-    globalThis.searchState = {
-        descShards: new Map(),
-        loadDesc: async function({descShard, descIndex}) {
-            if (descShard.promise === null) {
-                descShard.promise = new Promise((resolve, reject) => {
-                    descShard.resolve = resolve;
-                    const ds = descShard;
-                    const fname = `${ds.crate}-desc-${ds.shard}-${resource_suffix}.js`;
-                    fs.readFile(
-                        `${doc_folder}/search.desc/${descShard.crate}/${fname}`,
-                        (err, data) => {
-                            if (err) {
-                                reject(err);
-                            } else {
-                                eval(data.toString("utf8"));
-                            }
-                        },
-                    );
-                });
-            }
-            const list = await descShard.promise;
-            return list[descIndex];
-        },
-        loadedDescShard: function(crate, shard, data) {
-            this.descShards.get(crate)[shard].resolve(data.split("\n"));
-        },
-    };
-
+async function loadSearchJS(doc_folder, resource_suffix) {
     const staticFiles = path.join(doc_folder, "static.files");
-    const searchJs = fs.readdirSync(staticFiles).find(f => f.match(/search.*\.js$/));
+    const stringdexJs = mostRecentMatch(staticFiles, /stringdex.*\.js$/);
+    const stringdexModule = require(path.join(staticFiles, stringdexJs));
+    const searchJs = mostRecentMatch(staticFiles, /search-[0-9a-f]{8}.*\.js$/);
     const searchModule = require(path.join(staticFiles, searchJs));
-    searchModule.initSearch(searchIndex.searchIndex);
-    const docSearch = searchModule.docSearch;
+    globalThis.nonnull = (x, msg) => {
+        if (x === null) {
+            throw (msg || "unexpected null value!");
+        } else {
+            return x;
+        }
+    };
+    const { docSearch, DocSearch } = await searchModule.initSearch(
+        stringdexModule.Stringdex,
+        stringdexModule.RoaringBitmap,
+        {
+            loadRoot: callbacks => {
+                for (const key in callbacks) {
+                    if (Object.hasOwn(callbacks, key)) {
+                        globalThis[key] = callbacks[key];
+                    }
+                }
+                const rootJs = readFile(path.join(doc_folder, "search.index/root" +
+                    resource_suffix + ".js"));
+                eval(rootJs);
+            },
+            loadTreeByHash: hashHex => {
+                const shardJs = readFile(path.join(doc_folder, "search.index/" + hashHex + ".js"));
+                eval(shardJs);
+            },
+            loadDataByNameAndHash: (name, hashHex) => {
+                const shardJs = readFile(path.join(doc_folder, "search.index/" + name + "/" +
+                    hashHex + ".js"));
+                eval(shardJs);
+            },
+        },
+    );
     return {
         doSearch: async function(queryStr, filterCrate, currentCrate) {
-            const result = await docSearch.execQuery(searchModule.parseQuery(queryStr),
-                filterCrate, currentCrate);
+            const parsedQuery = DocSearch.parseQuery(queryStr);
+            const result = await docSearch.execQuery(parsedQuery, filterCrate, currentCrate);
+            const resultsTable = {};
             for (const tab in result) {
                 if (!Object.prototype.hasOwnProperty.call(result, tab)) {
                     continue;
                 }
-                if (!(result[tab] instanceof Array)) {
+                if (!isGeneratorObject(result[tab])) {
                     continue;
                 }
-                for (const entry of result[tab]) {
+                resultsTable[tab] = [];
+                for await (const entry of result[tab]) {
+                    const flatEntry = Object.assign({
+                        crate: entry.item.crate,
+                        name: entry.item.name,
+                        path: entry.item.modulePath,
+                        exactPath: entry.item.exactModulePath,
+                        ty: entry.item.ty,
+                    }, entry);
                     for (const key in entry) {
                         if (!Object.prototype.hasOwnProperty.call(entry, key)) {
                             continue;
                         }
-                        if (key === "displayTypeSignature" && entry.displayTypeSignature !== null) {
-                            const {type, mappedNames, whereClause} =
-                                await entry.displayTypeSignature;
-                            entry.displayType = arrayToCode(type);
-                            entry.displayMappedNames = [...mappedNames.entries()]
+                        if (key === "desc" && entry.desc !== null) {
+                            flatEntry.desc = await entry.desc;
+                        } else if (key === "displayTypeSignature" &&
+                            entry.displayTypeSignature !== null
+                        ) {
+                            flatEntry.displayTypeSignature = await entry.displayTypeSignature;
+                            const {
+                                type,
+                                mappedNames,
+                                whereClause,
+                            } = flatEntry.displayTypeSignature;
+                            flatEntry.displayType = arrayToCode(type);
+                            flatEntry.displayMappedNames = [...mappedNames.entries()]
                                 .map(([name, qname]) => {
                                     return `${name} = ${qname}`;
                                 }).join(", ");
-                            entry.displayWhereClause = [...whereClause.entries()]
+                            flatEntry.displayWhereClause = [...whereClause.entries()]
                                 .flatMap(([name, value]) => {
                                     if (value.length === 0) {
                                         return [];
@@ -489,16 +521,12 @@ function loadSearchJS(doc_folder, resource_suffix) {
                                 }).join(", ");
                         }
                     }
+                    resultsTable[tab].push(flatEntry);
                 }
             }
-            return result;
+            return { resultsTable, parsedQuery };
         },
-        getCorrections: function(queryStr, filterCrate, currentCrate) {
-            const parsedQuery = searchModule.parseQuery(queryStr);
-            docSearch.execQuery(parsedQuery, filterCrate, currentCrate);
-            return parsedQuery.correction;
-        },
-        parseQuery: searchModule.parseQuery,
+        parseQuery: DocSearch.parseQuery,
     };
 }
 
@@ -570,7 +598,7 @@ async function main(argv) {
         return 1;
     }
 
-    const parseAndSearch = loadSearchJS(
+    const parseAndSearch = await loadSearchJS(
         opts["doc_folder"],
         opts["resource_suffix"],
     );
@@ -579,14 +607,11 @@ async function main(argv) {
     const doSearch = function(queryStr, filterCrate) {
         return parseAndSearch.doSearch(queryStr, filterCrate, opts["crate_name"]);
     };
-    const getCorrections = function(queryStr, filterCrate) {
-        return parseAndSearch.getCorrections(queryStr, filterCrate, opts["crate_name"]);
-    };
 
     if (opts["test_file"].length !== 0) {
         for (const file of opts["test_file"]) {
             process.stdout.write(`Testing ${file} ... `);
-            errors += await runChecks(file, doSearch, parseAndSearch.parseQuery, getCorrections);
+            errors += await runChecks(file, doSearch, parseAndSearch.parseQuery);
         }
     } else if (opts["test_folder"].length !== 0) {
         for (const file of fs.readdirSync(opts["test_folder"])) {
@@ -595,7 +620,7 @@ async function main(argv) {
             }
             process.stdout.write(`Testing ${file} ... `);
             errors += await runChecks(path.join(opts["test_folder"], file), doSearch,
-                    parseAndSearch.parseQuery, getCorrections);
+                    parseAndSearch.parseQuery);
         }
     }
     return errors > 0 ? 1 : 0;
diff --git a/src/tools/rustfmt/Cargo.toml b/src/tools/rustfmt/Cargo.toml
index e497b792342..6392ffbe409 100644
--- a/src/tools/rustfmt/Cargo.toml
+++ b/src/tools/rustfmt/Cargo.toml
@@ -40,7 +40,7 @@ cargo_metadata = "0.18"
 clap = { version = "4.4.2", features = ["derive"] }
 clap-cargo = "0.12.0"
 diff = "0.1"
-dirs = "5.0"
+dirs = "6.0"
 getopts = "0.2"
 ignore = "0.4"
 itertools = "0.12"
diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs
index 10df6f96702..6555679c394 100644
--- a/src/tools/rustfmt/src/items.rs
+++ b/src/tools/rustfmt/src/items.rs
@@ -3600,7 +3600,7 @@ pub(crate) fn rewrite_extern_crate(
 pub(crate) fn is_mod_decl(item: &ast::Item) -> bool {
     !matches!(
         item.kind,
-        ast::ItemKind::Mod(_, _, ast::ModKind::Loaded(_, ast::Inline::Yes, _, _))
+        ast::ItemKind::Mod(_, _, ast::ModKind::Loaded(_, ast::Inline::Yes, _))
     )
 }
 
diff --git a/src/tools/rustfmt/src/modules.rs b/src/tools/rustfmt/src/modules.rs
index 3bc656b64b3..af9feccb32e 100644
--- a/src/tools/rustfmt/src/modules.rs
+++ b/src/tools/rustfmt/src/modules.rs
@@ -316,11 +316,12 @@ impl<'ast, 'psess, 'c> ModResolver<'ast, 'psess> {
             self.directory = directory;
         }
         match (sub_mod.ast_mod_kind, sub_mod.items) {
-            (Some(Cow::Borrowed(ast::ModKind::Loaded(items, _, _, _))), _) => {
+            (Some(Cow::Borrowed(ast::ModKind::Loaded(items, _, _))), _) => {
                 self.visit_mod_from_ast(items)
             }
-            (Some(Cow::Owned(ast::ModKind::Loaded(items, _, _, _))), _)
-            | (_, Cow::Owned(items)) => self.visit_mod_outside_ast(items),
+            (Some(Cow::Owned(ast::ModKind::Loaded(items, _, _))), _) | (_, Cow::Owned(items)) => {
+                self.visit_mod_outside_ast(items)
+            }
             (_, _) => Ok(()),
         }
     }
diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs
index 23d07c930d9..a3acbb218ff 100644
--- a/src/tools/rustfmt/src/visitor.rs
+++ b/src/tools/rustfmt/src/visitor.rs
@@ -942,7 +942,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
         let ident_str = rewrite_ident(&self.get_context(), ident).to_owned();
         self.push_str(&ident_str);
 
-        if let ast::ModKind::Loaded(ref items, ast::Inline::Yes, ref spans, _) = mod_kind {
+        if let ast::ModKind::Loaded(ref items, ast::Inline::Yes, ref spans) = mod_kind {
             let ast::ModSpans {
                 inner_span,
                 inject_use_span: _,
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index 858b058cb7d..80b6d54ce1c 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -592,6 +592,8 @@ pub fn check(root: &Path, cargo: &Path, bless: bool, bad: &mut bool) {
 
         if workspace == "library" {
             check_runtime_license_exceptions(&metadata, bad);
+            check_runtime_no_duplicate_dependencies(&metadata, bad);
+            check_runtime_no_proc_macros(&metadata, bad);
             checked_runtime_licenses = true;
         }
     }
@@ -790,6 +792,37 @@ fn check_license_exceptions(
     }
 }
 
+fn check_runtime_no_duplicate_dependencies(metadata: &Metadata, bad: &mut bool) {
+    let mut seen_pkgs = HashSet::new();
+    for pkg in &metadata.packages {
+        if pkg.source.is_none() {
+            continue;
+        }
+
+        if !seen_pkgs.insert(&*pkg.name) {
+            tidy_error!(
+                bad,
+                "duplicate package `{}` is not allowed for the standard library",
+                pkg.name
+            );
+        }
+    }
+}
+
+fn check_runtime_no_proc_macros(metadata: &Metadata, bad: &mut bool) {
+    for pkg in &metadata.packages {
+        if pkg.targets.iter().any(|target| target.is_proc_macro()) {
+            tidy_error!(
+                bad,
+                "proc macro `{}` is not allowed as standard library dependency.\n\
+                Using proc macros in the standard library would break cross-compilation \
+                as proc-macros don't get shipped for the host tuple.",
+                pkg.name
+            );
+        }
+    }
+}
+
 /// Checks the dependency of `restricted_dependency_crates` at the given path. Changes `bad` to
 /// `true` if a check failed.
 ///
diff --git a/src/tools/tidy/src/extra_checks/mod.rs b/src/tools/tidy/src/extra_checks/mod.rs
index f90f716cd95..31169ec5967 100644
--- a/src/tools/tidy/src/extra_checks/mod.rs
+++ b/src/tools/tidy/src/extra_checks/mod.rs
@@ -41,7 +41,6 @@ const RUFF_CONFIG_PATH: &[&str] = &["src", "tools", "tidy", "config", "ruff.toml
 const RUFF_CACHE_PATH: &[&str] = &["cache", "ruff_cache"];
 const PIP_REQ_PATH: &[&str] = &["src", "tools", "tidy", "config", "requirements.txt"];
 
-// this must be kept in sync with with .github/workflows/spellcheck.yml
 const SPELLCHECK_DIRS: &[&str] = &["compiler", "library", "src/bootstrap", "src/librustdoc"];
 
 pub fn check(
@@ -51,6 +50,7 @@ pub fn check(
     librustdoc_path: &Path,
     tools_path: &Path,
     npm: &Path,
+    cargo: &Path,
     bless: bool,
     extra_checks: Option<&str>,
     pos_args: &[String],
@@ -63,6 +63,7 @@ pub fn check(
         librustdoc_path,
         tools_path,
         npm,
+        cargo,
         bless,
         extra_checks,
         pos_args,
@@ -78,6 +79,7 @@ fn check_impl(
     librustdoc_path: &Path,
     tools_path: &Path,
     npm: &Path,
+    cargo: &Path,
     bless: bool,
     extra_checks: Option<&str>,
     pos_args: &[String],
@@ -293,7 +295,7 @@ fn check_impl(
         } else {
             eprintln!("spellcheck files");
         }
-        spellcheck_runner(&args)?;
+        spellcheck_runner(root_path, &outdir, &cargo, &args)?;
     }
 
     if js_lint || js_typecheck {
@@ -576,34 +578,25 @@ fn shellcheck_runner(args: &[&OsStr]) -> Result<(), Error> {
     if status.success() { Ok(()) } else { Err(Error::FailedCheck("shellcheck")) }
 }
 
-/// Check that spellchecker is installed then run it at the given path
-fn spellcheck_runner(args: &[&str]) -> Result<(), Error> {
-    // sync version with .github/workflows/spellcheck.yml
-    let expected_version = "typos-cli 1.34.0";
-    match Command::new("typos").arg("--version").output() {
-        Ok(o) => {
-            let stdout = String::from_utf8_lossy(&o.stdout);
-            if stdout.trim() != expected_version {
-                return Err(Error::Version {
-                    program: "typos",
-                    required: expected_version,
-                    installed: stdout.trim().to_string(),
-                });
+/// Ensure that spellchecker is installed then run it at the given path
+fn spellcheck_runner(
+    src_root: &Path,
+    outdir: &Path,
+    cargo: &Path,
+    args: &[&str],
+) -> Result<(), Error> {
+    let bin_path =
+        crate::ensure_version_or_cargo_install(outdir, cargo, "typos-cli", "typos", "1.34.0")?;
+    match Command::new(bin_path).current_dir(src_root).args(args).status() {
+        Ok(status) => {
+            if status.success() {
+                Ok(())
+            } else {
+                Err(Error::FailedCheck("typos"))
             }
         }
-        Err(e) if e.kind() == io::ErrorKind::NotFound => {
-            return Err(Error::MissingReq(
-                "typos",
-                "spellcheck file checks",
-                // sync version with .github/workflows/spellcheck.yml
-                Some("install tool via `cargo install typos-cli@1.34.0`".to_owned()),
-            ));
-        }
-        Err(e) => return Err(e.into()),
+        Err(err) => Err(Error::Generic(format!("failed to run typos tool: {err:?}"))),
     }
-
-    let status = Command::new("typos").args(args).status()?;
-    if status.success() { Ok(()) } else { Err(Error::FailedCheck("typos")) }
 }
 
 /// Check git for tracked files matching an extension
diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs
index 4ea9d051ddb..37713cbf75e 100644
--- a/src/tools/tidy/src/lib.rs
+++ b/src/tools/tidy/src/lib.rs
@@ -4,7 +4,9 @@
 //! to be used by tools.
 
 use std::ffi::OsStr;
+use std::path::{Path, PathBuf};
 use std::process::Command;
+use std::{env, io};
 
 use build_helper::ci::CiEnv;
 use build_helper::git::{GitConfig, get_closest_upstream_commit};
@@ -180,6 +182,70 @@ pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
     !v.is_empty()
 }
 
+/// If the given executable is installed with the given version, use that,
+/// otherwise install via cargo.
+pub fn ensure_version_or_cargo_install(
+    build_dir: &Path,
+    cargo: &Path,
+    pkg_name: &str,
+    bin_name: &str,
+    version: &str,
+) -> io::Result<PathBuf> {
+    // ignore the process exit code here and instead just let the version number check fail.
+    // we also importantly don't return if the program wasn't installed,
+    // instead we want to continue to the fallback.
+    'ck: {
+        // FIXME: rewrite as if-let chain once this crate is 2024 edition.
+        let Ok(output) = Command::new(bin_name).arg("--version").output() else {
+            break 'ck;
+        };
+        let Ok(s) = str::from_utf8(&output.stdout) else {
+            break 'ck;
+        };
+        let Some(v) = s.trim().split_whitespace().last() else {
+            break 'ck;
+        };
+        if v == version {
+            return Ok(PathBuf::from(bin_name));
+        }
+    }
+
+    let tool_root_dir = build_dir.join("misc-tools");
+    let tool_bin_dir = tool_root_dir.join("bin");
+    eprintln!("building external tool {bin_name} from package {pkg_name}@{version}");
+    // use --force to ensure that if the required version is bumped, we update it.
+    // use --target-dir to ensure we have a build cache so repeated invocations aren't slow.
+    // modify PATH so that cargo doesn't print a warning telling the user to modify the path.
+    let cargo_exit_code = Command::new(cargo)
+        .args(["install", "--locked", "--force", "--quiet"])
+        .arg("--root")
+        .arg(&tool_root_dir)
+        .arg("--target-dir")
+        .arg(tool_root_dir.join("target"))
+        .arg(format!("{pkg_name}@{version}"))
+        .env(
+            "PATH",
+            env::join_paths(
+                env::split_paths(&env::var("PATH").unwrap())
+                    .chain(std::iter::once(tool_bin_dir.clone())),
+            )
+            .expect("build dir contains invalid char"),
+        )
+        .env("RUSTFLAGS", "-Copt-level=0")
+        .spawn()?
+        .wait()?;
+    if !cargo_exit_code.success() {
+        return Err(io::Error::other("cargo install failed"));
+    }
+    let bin_path = tool_bin_dir.join(bin_name);
+    assert!(
+        matches!(bin_path.try_exists(), Ok(true)),
+        "cargo install did not produce the expected binary"
+    );
+    eprintln!("finished building tool {bin_name}");
+    Ok(bin_path)
+}
+
 pub mod alphabetical;
 pub mod bins;
 pub mod debug_artifacts;
diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
index cd2567ddb64..bfe30258915 100644
--- a/src/tools/tidy/src/main.rs
+++ b/src/tools/tidy/src/main.rs
@@ -184,6 +184,7 @@ fn main() {
             &librustdoc_path,
             &tools_path,
             &npm,
+            &cargo,
             bless,
             extra_checks,
             pos_args