From 97a06feab0869c9a57a3a63c840d9e3e6664aea8 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 12 Jan 2026 22:33:08 +0200 Subject: [PATCH 01/27] add range-diff triagebot option --- src/doc/rustc-dev-guide/triagebot.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/doc/rustc-dev-guide/triagebot.toml b/src/doc/rustc-dev-guide/triagebot.toml index 974f4cd3dd969..f894456d0de37 100644 --- a/src/doc/rustc-dev-guide/triagebot.toml +++ b/src/doc/rustc-dev-guide/triagebot.toml @@ -86,3 +86,6 @@ rustc-dev-guide = [ "@jyn514", "@tshepang", ] + +# Make rebases more easy to read: https://forge.rust-lang.org/triagebot/range-diff.html +[range-diff] From 513b6ad231859f0b3c7953c454f9b209d58c25a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 13 Jan 2026 17:41:55 +0100 Subject: [PATCH 02/27] Remove references to homu and fix bors delegation command --- src/doc/rustc-dev-guide/src/about-this-guide.md | 4 ++-- src/doc/rustc-dev-guide/src/compiler-team.md | 4 ++-- src/doc/rustc-dev-guide/src/contributing.md | 2 +- src/doc/rustc-dev-guide/src/tests/ci.md | 11 +++-------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/about-this-guide.md b/src/doc/rustc-dev-guide/src/about-this-guide.md index 2082481a200e6..9d493e0cb0656 100644 --- a/src/doc/rustc-dev-guide/src/about-this-guide.md +++ b/src/doc/rustc-dev-guide/src/about-this-guide.md @@ -103,9 +103,9 @@ You might also find the following sites useful: [tlgba]: https://tomlee.co/2014/04/a-more-detailed-tour-of-the-rust-compiler/ [ro]: https://www.rustaceans.org/ [rctd]: tests/intro.md -[cheatsheet]: https://bors.rust-lang.org/ +[cheatsheet]: https://bors.rust-lang.org/help [Miri]: https://github.com/rust-lang/miri -[@bors]: https://github.com/bors +[@bors]: https://github.com/rust-lang/bors [a GitHub repository]: https://github.com/rust-lang/rustc-dev-guide/ [rustc API docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle [Forge]: https://forge.rust-lang.org/ diff --git a/src/doc/rustc-dev-guide/src/compiler-team.md b/src/doc/rustc-dev-guide/src/compiler-team.md index dfc1e7eedf622..495bd22da4d85 100644 --- a/src/doc/rustc-dev-guide/src/compiler-team.md +++ b/src/doc/rustc-dev-guide/src/compiler-team.md @@ -96,9 +96,9 @@ Once you have made a number of individual PRs to rustc, we will often offer r+ privileges. This means that you have the right to instruct "bors" (the robot that manages which PRs get landed into rustc) to merge a PR -([here are some instructions for how to talk to bors][homu-guide]). +([here are some instructions for how to talk to bors][bors-guide]). -[homu-guide]: https://bors.rust-lang.org/ +[bors-guide]: https://bors.rust-lang.org/ The guidelines for reviewers are as follows: diff --git a/src/doc/rustc-dev-guide/src/contributing.md b/src/doc/rustc-dev-guide/src/contributing.md index 40ad58e025c17..46d0dc23394a2 100644 --- a/src/doc/rustc-dev-guide/src/contributing.md +++ b/src/doc/rustc-dev-guide/src/contributing.md @@ -283,7 +283,7 @@ this can take a while and the queue can sometimes be long. Also, note that PRs are never merged by hand. [@rustbot]: https://github.com/rustbot -[@bors]: https://github.com/bors +[@bors]: https://github.com/rust-lang/bors ### Opening a PR diff --git a/src/doc/rustc-dev-guide/src/tests/ci.md b/src/doc/rustc-dev-guide/src/tests/ci.md index e132946ae83ee..ce80b07fe08db 100644 --- a/src/doc/rustc-dev-guide/src/tests/ci.md +++ b/src/doc/rustc-dev-guide/src/tests/ci.md @@ -198,7 +198,7 @@ to help make the perf comparison as fair as possible. > > 3. Run the prescribed try jobs with `@bors try`. As aforementioned, this > requires the user to either (1) have `try` permissions or (2) be delegated -> with `try` permissions by `@bors delegate` by someone who has `try` +> with `try` permissions by `@bors delegate=try` by someone who has `try` > permissions. > > Note that this is usually easier to do than manually edit [`jobs.yml`]. @@ -213,10 +213,7 @@ the corresponding PR. Multiple try builds can execute concurrently across different PRs, but there can be at most a single try build running on a single PR at any given time. -Note that try builds are handled using the [new bors] implementation. - [rustc-perf]: https://github.com/rust-lang/rustc-perf -[new bors]: https://github.com/rust-lang/bors ### Modifying CI jobs @@ -281,8 +278,7 @@ Breakages like these usually happen when another, incompatible PR is merged after the build happened. To ensure a `main` branch that works all the time, we forbid manual merges. -Instead, all PRs have to be approved through our bot, [bors] (the software -behind it is called [homu]). +Instead, all PRs have to be approved through our bot, [bors]. All the approved PRs are put in a [merge queue] (sorted by priority and creation date) and are automatically tested one at the time. If all the builders are green, the PR is merged, otherwise the failure is @@ -465,8 +461,7 @@ To do this: [`jobs.yml`]: https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml [`.github/workflows/ci.yml`]: https://github.com/rust-lang/rust/blob/HEAD/.github/workflows/ci.yml [`src/ci/citool`]: https://github.com/rust-lang/rust/blob/HEAD/src/ci/citool -[bors]: https://github.com/bors -[homu]: https://github.com/rust-lang/homu +[bors]: https://github.com/rust-lang/bors [merge queue]: https://bors.rust-lang.org/queue/rust [dist-x86_64-linux]: https://github.com/rust-lang/rust/blob/HEAD/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile [the GitHub Actions workflows page]: https://github.com/rust-lang/rust/actions From bc751adcdb7cd5cce59a6aec36e74b959132e0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Dom=C3=ADnguez?= Date: Sun, 4 Jan 2026 17:26:21 +0100 Subject: [PATCH 03/27] Minor doc and ty fixes --- compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs index b8eb4f0382167..7817755dafe41 100644 --- a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs +++ b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs @@ -445,9 +445,8 @@ fn declare_offload_fn<'ll>( // the gpu. For now, we only handle the data transfer part of it. // If two consecutive kernels use the same memory, we still move it to the host and back to the gpu. // Since in our frontend users (by default) don't have to specify data transfer, this is something -// we should optimize in the future! We also assume that everything should be copied back and forth, -// but sometimes we can directly zero-allocate on the device and only move back, or if something is -// immutable, we might only copy it to the device, but not back. +// we should optimize in the future! In some cases we can directly zero-allocate ont he device and +// only move data back, or if something is immutable, we might only copy it to the device. // // Current steps: // 0. Alloca some variables for the following steps From 2c9c5d14a210767f83358c6cb17c7a81ec7ae41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Dom=C3=ADnguez?= Date: Tue, 6 Jan 2026 19:14:14 +0100 Subject: [PATCH 04/27] Allow bounded types --- compiler/rustc_codegen_llvm/src/intrinsic.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 481f75f337d63..59cbcd78dd0f9 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1388,7 +1388,8 @@ fn codegen_offload<'ll, 'tcx>( let args = get_args_from_tuple(bx, args[3], fn_target); let target_symbol = symbol_name_for_instance_in_crate(tcx, fn_target, LOCAL_CRATE); - let sig = tcx.fn_sig(fn_target.def_id()).skip_binder().skip_binder(); + let sig = tcx.fn_sig(fn_target.def_id()).instantiate_identity(); + let sig = tcx.instantiate_bound_regions_with_erased(sig); let inputs = sig.inputs(); let metadata = inputs.iter().map(|ty| OffloadMetadata::from_ty(tcx, *ty)).collect::>(); From 4ded6397b376b0a005c26381ed92c944e3702019 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 14 Jan 2026 00:45:16 +0100 Subject: [PATCH 05/27] Add new "hide deprecated items" setting in rustdoc --- src/librustdoc/clean/auto_trait.rs | 1 + src/librustdoc/clean/blanket_impl.rs | 3 ++ src/librustdoc/clean/inline.rs | 3 ++ src/librustdoc/clean/mod.rs | 4 ++ src/librustdoc/clean/types.rs | 8 ++++ src/librustdoc/html/render/mod.rs | 33 +++++++++++---- src/librustdoc/html/render/print_item.rs | 49 +++++++++++++++------- src/librustdoc/html/static/css/rustdoc.css | 9 ++++ src/librustdoc/html/static/js/search.js | 3 ++ src/librustdoc/html/static/js/settings.js | 11 +++++ src/librustdoc/html/static/js/storage.js | 3 ++ src/librustdoc/json/conversions.rs | 3 +- 12 files changed, 107 insertions(+), 23 deletions(-) diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 6c67916571a40..847e688d03d0a 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -126,6 +126,7 @@ fn synthesize_auto_trait_impl<'tcx>( items: Vec::new(), polarity, kind: clean::ImplKind::Auto, + is_deprecated: false, })), item_id: clean::ItemId::Auto { trait_: trait_def_id, for_: item_def_id }, cfg: None, diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs index ddfce7aeb92da..de45922e856f7 100644 --- a/src/librustdoc/clean/blanket_impl.rs +++ b/src/librustdoc/clean/blanket_impl.rs @@ -117,6 +117,9 @@ pub(crate) fn synthesize_blanket_impls( None, None, ))), + is_deprecated: tcx + .lookup_deprecation(impl_def_id) + .is_some_and(|deprecation| deprecation.is_in_effect()), })), cfg: None, inline_stmt_id: None, diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index c86a655c083a5..19974f4847d47 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -645,6 +645,9 @@ pub(crate) fn build_impl( } else { ImplKind::Normal }, + is_deprecated: tcx + .lookup_deprecation(did) + .is_some_and(|deprecation| deprecation.is_in_effect()), })), merged_attrs, cfg, diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 817eda4c52ecc..0e970a042ff0c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2882,6 +2882,9 @@ fn clean_impl<'tcx>( )), _ => None, }); + let is_deprecated = tcx + .lookup_deprecation(def_id.to_def_id()) + .is_some_and(|deprecation| deprecation.is_in_effect()); let mut make_item = |trait_: Option, for_: Type, items: Vec| { let kind = ImplItem(Box::new(Impl { safety: match impl_.of_trait { @@ -2902,6 +2905,7 @@ fn clean_impl<'tcx>( } else { ImplKind::Normal }, + is_deprecated, })); Item::from_def_id_and_parts(def_id.to_def_id(), None, kind, cx) }; diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index c3bafd3db13ac..93863ea41396a 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -427,6 +427,10 @@ impl Item { }) } + pub(crate) fn is_deprecated(&self, tcx: TyCtxt<'_>) -> bool { + self.deprecation(tcx).is_some_and(|deprecation| deprecation.is_in_effect()) + } + pub(crate) fn inner_docs(&self, tcx: TyCtxt<'_>) -> bool { self.item_id.as_def_id().map(|did| inner_docs(tcx.get_all_attrs(did))).unwrap_or(false) } @@ -1270,6 +1274,9 @@ impl Trait { pub(crate) fn is_dyn_compatible(&self, tcx: TyCtxt<'_>) -> bool { tcx.is_dyn_compatible(self.def_id) } + pub(crate) fn is_deprecated(&self, tcx: TyCtxt<'_>) -> bool { + tcx.lookup_deprecation(self.def_id).is_some_and(|deprecation| deprecation.is_in_effect()) + } } #[derive(Clone, Debug)] @@ -2254,6 +2261,7 @@ pub(crate) struct Impl { pub(crate) items: Vec, pub(crate) polarity: ty::ImplPolarity, pub(crate) kind: ImplKind, + pub(crate) is_deprecated: bool, } impl Impl { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 63de870f07f45..106aefa4626b9 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1794,12 +1794,14 @@ fn render_impl( let mut info_buffer = String::new(); let mut short_documented = true; + let mut trait_item_deprecated = false; if render_method_item { if !is_default_item { if let Some(t) = trait_ { // The trait item may have been stripped so we might not // find any documentation or stability for it. if let Some(it) = t.items.iter().find(|i| i.name == item.name) { + trait_item_deprecated = it.is_deprecated(cx.tcx()); // We need the stability of the item from the trait // because impls can't have a stability. if !item.doc_value().is_empty() { @@ -1839,10 +1841,20 @@ fn render_impl( Either::Right(boring) }; + let mut deprecation_class = if trait_item_deprecated || item.is_deprecated(cx.tcx()) { + " deprecated" + } else { + "" + }; + let toggled = !doc_buffer.is_empty(); if toggled { let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; - write!(w, "
")?; + write!( + w, + "
" + )?; + deprecation_class = ""; } match &item.kind { clean::MethodItem(..) | clean::RequiredMethodItem(_) => { @@ -1859,7 +1871,7 @@ fn render_impl( .map(|item| format!("{}.{name}", item.type_())); write!( w, - "
\ + "
\ {}", render_rightside(cx, item, render_mode) )?; @@ -1885,7 +1897,7 @@ fn render_impl( let id = cx.derive_id(&source_id); write!( w, - "
\ + "
\ {}", render_rightside(cx, item, render_mode) )?; @@ -1912,7 +1924,7 @@ fn render_impl( let id = cx.derive_id(&source_id); write!( w, - "
\ + "
\ {}", render_rightside(cx, item, render_mode), )?; @@ -1944,7 +1956,7 @@ fn render_impl( let id = cx.derive_id(&source_id); write!( w, - "
\ + "
\ {}", render_rightside(cx, item, render_mode), )?; @@ -1971,7 +1983,7 @@ fn render_impl( let id = cx.derive_id(&source_id); write!( w, - "
\ + "
\ {}", render_rightside(cx, item, render_mode), )?; @@ -2143,11 +2155,18 @@ fn render_impl( } if render_mode == RenderMode::Normal { let toggled = !(impl_items.is_empty() && default_impl_items.is_empty()); + let deprecation_attr = if impl_.is_deprecated + || trait_.is_some_and(|trait_| trait_.is_deprecated(cx.tcx())) + { + " deprecated" + } else { + "" + }; if toggled { close_tags.push("
"); write!( w, - "
\ + "
\ ", if rendering_params.toggle_open_by_default { " open" } else { "" } )?; diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 84e93a479b5b5..55e36c462f967 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -217,6 +217,10 @@ fn toggle_close(mut w: impl fmt::Write) { } fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> impl fmt::Display { + fn deprecation_class_attr(is_deprecated: bool) -> &'static str { + if is_deprecated { " class=\"deprecated\"" } else { "" } + } + fmt::from_fn(|w| { write!(w, "{}", document(cx, item, None, HeadingOffset::H2))?; @@ -370,11 +374,18 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i write!(w, "")? } clean::ImportItem(ref import) => { - let stab_tags = - import.source.did.map_or_else(String::new, |import_def_id| { - print_extra_info_tags(tcx, myitem, item, Some(import_def_id)) - .to_string() - }); + let (stab_tags, deprecation) = match import.source.did { + Some(import_def_id) => { + let stab_tags = + print_extra_info_tags(tcx, myitem, item, Some(import_def_id)) + .to_string(); + let deprecation = tcx + .lookup_deprecation(import_def_id) + .is_some_and(|deprecation| deprecation.is_in_effect()); + (stab_tags, deprecation) + } + None => (String::new(), item.is_deprecated(tcx)), + }; let id = match import.kind { clean::ImportKind::Simple(s) => { format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) @@ -383,8 +394,8 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i }; write!( w, - "\ - " + "", + deprecation_attr = deprecation_class_attr(deprecation) )?; render_attributes_in_code(w, myitem, "", cx)?; write!( @@ -396,9 +407,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i )?; } _ => { - if myitem.name.is_none() { - continue; - } + let Some(item_name) = myitem.name else { continue }; let unsafety_flag = match myitem.kind { clean::FunctionItem(_) | clean::ForeignFunctionItem(..) @@ -431,9 +440,10 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i .into_string(); let (docs_before, docs_after) = if docs.is_empty() { ("", "") } else { ("
", "
") }; + let deprecation_attr = deprecation_class_attr(myitem.is_deprecated(tcx)); write!( w, - "
\ + "\ \ {name}\ \ @@ -442,12 +452,12 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i {stab_tags}\
\ {docs_before}{docs}{docs_after}", - name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), + name = EscapeBodyTextWithWbr(item_name.as_str()), visibility_and_hidden = visibility_and_hidden, stab_tags = print_extra_info_tags(tcx, myitem, item, None), class = type_, unsafety_flag = unsafety_flag, - href = print_item_path(type_, myitem.name.unwrap().as_str()), + href = print_item_path(type_, item_name.as_str()), title1 = myitem.type_(), title2 = full_path(cx, myitem), )?; @@ -778,15 +788,24 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: let content = document_full(m, cx, HeadingOffset::H5).to_string(); + let mut deprecation_class = + if m.is_deprecated(cx.tcx()) { " deprecated" } else { "" }; + let toggled = !content.is_empty(); if toggled { let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; - write!(w, "
")?; + write!( + w, + "
" + )?; + deprecation_class = ""; } write!( w, - "
\ + "
\ {}\

{}

", render_rightside(cx, m, RenderMode::Normal), diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index b770a0e2a0e41..73eb260a2b8e2 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -2642,6 +2642,15 @@ However, it's not needed with smaller screen width because the doc/code block is } } +/* Items on module pages */ +.hide-deprecated-items dt.deprecated, +.hide-deprecated-items dt.deprecated + dd, +/* Items on item pages */ +.hide-deprecated-items .deprecated, +.hide-deprecated-items .deprecated + .item-info { + display: none; +} + /* WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY If you update this line, then you also need to update the line with the same warning diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index b2880a2f4c2b9..9961c1447ec2a 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -4920,6 +4920,9 @@ async function addTab(results, query, display, finishedCallback, isTypeSearch) { const link = document.createElement("a"); link.className = "result-" + type; + if (obj.item.deprecated) { + link.className += " deprecated"; + } link.href = obj.href; const resultName = document.createElement("span"); diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 347d3d0750ece..b28b76b0d2e78 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -78,6 +78,12 @@ removeClass(document.documentElement, "word-wrap-source-code"); } break; + case "hide-deprecated-items": + if (value === true) { + addClass(document.documentElement, "hide-deprecated-items"); + } else { + removeClass(document.documentElement, "hide-deprecated-items"); + } } } @@ -274,6 +280,11 @@ "js_name": "word-wrap-source-code", "default": false, }, + { + "name": "Hide deprecated items", + "js_name": "hide-deprecated-items", + "default": false, + }, ]; // Then we build the DOM. diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 40ab8be03c93d..7cc70374378be 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -334,6 +334,9 @@ if (getSettingValue("sans-serif-fonts") === "true") { if (getSettingValue("word-wrap-source-code") === "true") { addClass(document.documentElement, "word-wrap-source-code"); } +if (getSettingValue("hide-deprecated-items") === "true") { + addClass(document.documentElement, "hide-deprecated-items"); +} function updateSidebarWidth() { const desktopSidebarWidth = getSettingValue("desktop-sidebar-width"); if (desktopSidebarWidth && desktopSidebarWidth !== "null") { diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 892cc483dbd6f..2edf7891be400 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -711,7 +711,8 @@ impl FromClean for PolyTrait { impl FromClean for Impl { fn from_clean(impl_: &clean::Impl, renderer: &JsonRenderer<'_>) -> Self { let provided_trait_methods = impl_.provided_trait_methods(renderer.tcx); - let clean::Impl { safety, generics, trait_, for_, items, polarity, kind } = impl_; + let clean::Impl { safety, generics, trait_, for_, items, polarity, kind, is_deprecated: _ } = + impl_; // FIXME: use something like ImplKind in JSON? let (is_synthetic, blanket_impl) = match kind { clean::ImplKind::Normal | clean::ImplKind::FakeVariadic => (false, None), From 316627ee24c2214037631c227dc073bde46001e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 16 Dec 2025 21:19:20 +0100 Subject: [PATCH 06/27] Refactor `Enzyme` step --- src/bootstrap/src/core/build_steps/compile.rs | 22 +++------ src/bootstrap/src/core/build_steps/llvm.rs | 47 +++++++++++++++---- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 11f2a28bb935c..651ff03a8690a 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -2292,23 +2292,13 @@ impl Step for Assemble { builder.compiler(target_compiler.stage - 1, builder.config.host_target); // Build enzyme - if builder.config.llvm_enzyme && !builder.config.dry_run() { + if builder.config.llvm_enzyme { debug!("`llvm_enzyme` requested"); - let enzyme_install = builder.ensure(llvm::Enzyme { target: build_compiler.host }); - if let Some(llvm_config) = builder.llvm_config(builder.config.host_target) { - let llvm_version_major = llvm::get_llvm_version_major(builder, &llvm_config); - let lib_ext = std::env::consts::DLL_EXTENSION; - let libenzyme = format!("libEnzyme-{llvm_version_major}"); - let src_lib = - enzyme_install.join("build/Enzyme").join(&libenzyme).with_extension(lib_ext); - let libdir = builder.sysroot_target_libdir(build_compiler, build_compiler.host); - let target_libdir = - builder.sysroot_target_libdir(target_compiler, target_compiler.host); - let dst_lib = libdir.join(&libenzyme).with_extension(lib_ext); - let target_dst_lib = target_libdir.join(&libenzyme).with_extension(lib_ext); - builder.copy_link(&src_lib, &dst_lib, FileType::NativeLibrary); - builder.copy_link(&src_lib, &target_dst_lib, FileType::NativeLibrary); - } + let enzyme = builder.ensure(llvm::Enzyme { target: build_compiler.host }); + let target_libdir = + builder.sysroot_target_libdir(target_compiler, target_compiler.host); + let target_dst_lib = target_libdir.join(enzyme.enzyme_filename()); + builder.copy_link(&enzyme.enzyme_path(), &target_dst_lib, FileType::NativeLibrary); } if builder.config.llvm_offload && !builder.config.dry_run() { diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index c3935d9810e99..f6a763b940753 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -17,6 +17,7 @@ use std::{env, fs}; use build_helper::exit; use build_helper::git::PathFreshness; +use crate::core::build_steps::llvm; use crate::core::builder::{Builder, RunConfig, ShouldRun, Step, StepMetadata}; use crate::core::config::{Config, TargetSelection}; use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash}; @@ -1077,13 +1078,28 @@ impl Step for OmpOffload { } } +#[derive(Clone)] +pub struct BuiltEnzyme { + /// Path to the libEnzyme dylib. + enzyme: PathBuf, +} + +impl BuiltEnzyme { + pub fn enzyme_path(&self) -> PathBuf { + self.enzyme.clone() + } + pub fn enzyme_filename(&self) -> String { + self.enzyme.file_name().unwrap().to_str().unwrap().to_owned() + } +} + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Enzyme { pub target: TargetSelection, } impl Step for Enzyme { - type Output = PathBuf; + type Output = BuiltEnzyme; const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -1095,16 +1111,16 @@ impl Step for Enzyme { } /// Compile Enzyme for `target`. - fn run(self, builder: &Builder<'_>) -> PathBuf { + fn run(self, builder: &Builder<'_>) -> Self::Output { builder.require_submodule( "src/tools/enzyme", Some("The Enzyme sources are required for autodiff."), ); + let target = self.target; + if builder.config.dry_run() { - let out_dir = builder.enzyme_out(self.target); - return out_dir; + return BuiltEnzyme { enzyme: builder.config.tempdir().join("enzyme-dryrun") }; } - let target = self.target; let LlvmResult { host_llvm_config, llvm_cmake_dir } = builder.ensure(Llvm { target }); @@ -1120,6 +1136,12 @@ impl Step for Enzyme { let out_dir = builder.enzyme_out(target); let stamp = BuildStamp::new(&out_dir).with_prefix("enzyme").add_stamp(smart_stamp_hash); + let llvm_version_major = llvm::get_llvm_version_major(builder, &host_llvm_config); + let lib_ext = std::env::consts::DLL_EXTENSION; + let libenzyme = format!("libEnzyme-{llvm_version_major}"); + let build_dir = out_dir.join("lib"); + let dylib = build_dir.join(&libenzyme).with_extension(lib_ext); + trace!("checking build stamp to see if we need to rebuild enzyme artifacts"); if stamp.is_up_to_date() { trace!(?out_dir, "enzyme build artifacts are up to date"); @@ -1133,7 +1155,7 @@ impl Step for Enzyme { stamp.path().display() )); } - return out_dir; + return BuiltEnzyme { enzyme: dylib }; } if !builder.config.dry_run() && !llvm_cmake_dir.is_dir() { @@ -1149,7 +1171,6 @@ impl Step for Enzyme { let _time = helpers::timeit(builder); t!(fs::create_dir_all(&out_dir)); - builder.config.update_submodule("src/tools/enzyme"); let mut cfg = cmake::Config::new(builder.src.join("src/tools/enzyme/enzyme/")); // Enzyme devs maintain upstream compatibility, but only fix deprecations when they are about // to turn into a hard error. As such, Enzyme generates various warnings which could make it @@ -1178,8 +1199,18 @@ impl Step for Enzyme { cfg.build(); + // At this point, `out_dir` should contain the built libEnzyme-. + // file. + if !dylib.exists() { + eprintln!( + "`{libenzyme}` not found in `{}`. Either the build has failed or Enzyme was built with a wrong version of LLVM", + build_dir.display() + ); + exit!(1); + } + t!(stamp.write()); - out_dir + BuiltEnzyme { enzyme: dylib } } } From 85e01e3c4e328a63b39ea1189de03b79cc02b0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 16 Dec 2025 21:55:56 +0100 Subject: [PATCH 07/27] Add dist step for `Enzyme` --- src/bootstrap/src/core/build_steps/dist.rs | 49 ++++++++++++++++++++++ src/bootstrap/src/core/builder/mod.rs | 1 + src/bootstrap/src/utils/tarball.rs | 3 ++ 3 files changed, 53 insertions(+) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index cfcb144e0993e..8ba05a7fa3a7f 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -2717,6 +2717,55 @@ impl Step for LlvmBitcodeLinker { } } +/// Distributes the `enzyme` library so that it can be used by a compiler whose host +/// is `target`. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Enzyme { + /// Enzyme will by usable by rustc on this host. + pub target: TargetSelection, +} + +impl Step for Enzyme { + type Output = Option; + const IS_HOST: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.alias("enzyme") + } + + fn is_default_step(builder: &Builder<'_>) -> bool { + builder.config.llvm_enzyme + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(Enzyme { target: run.target }); + } + + fn run(self, builder: &Builder<'_>) -> Option { + // This prevents Enzyme from being built for "dist" + // or "install" on the stable/beta channels. It is not yet stable and + // should not be included. + if !builder.build.unstable_features() { + return None; + } + + let target = self.target; + + let enzyme = builder.ensure(llvm::Enzyme { target }); + + let target_libdir = format!("lib/rustlib/{}/lib", target.triple); + + // Prepare the image directory + let mut tarball = Tarball::new(builder, "enzyme", &target.triple); + tarball.set_overlay(OverlayKind::Enzyme); + tarball.is_preview(true); + + tarball.add_file(enzyme.enzyme_path(), target_libdir, FileType::NativeLibrary); + + Some(tarball.generate()) + } +} + /// Tarball intended for internal consumption to ease rustc/std development. /// /// Should not be considered stable by end users. diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index f63b8e0445509..14cae2d0efc4b 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -980,6 +980,7 @@ impl<'a> Builder<'a> { dist::LlvmTools, dist::LlvmBitcodeLinker, dist::RustDev, + dist::Enzyme, dist::Bootstrap, dist::Extended, // It seems that PlainSourceTarball somehow changes how some of the tools diff --git a/src/bootstrap/src/utils/tarball.rs b/src/bootstrap/src/utils/tarball.rs index cd6a03c84870c..17d75e83daeac 100644 --- a/src/bootstrap/src/utils/tarball.rs +++ b/src/bootstrap/src/utils/tarball.rs @@ -28,6 +28,7 @@ pub(crate) enum OverlayKind { RustcCodegenGcc, Gcc, LlvmBitcodeLinker, + Enzyme, } impl OverlayKind { @@ -37,6 +38,7 @@ impl OverlayKind { OverlayKind::Llvm => { &["src/llvm-project/llvm/LICENSE.TXT", "src/llvm-project/llvm/README.txt"] } + OverlayKind::Enzyme => &["src/tools/enzyme/LICENSE", "src/tools/enzyme/Readme.md"], OverlayKind::Cargo => &[ "src/tools/cargo/README.md", "src/tools/cargo/LICENSE-MIT", @@ -111,6 +113,7 @@ impl OverlayKind { OverlayKind::RustcCodegenGcc => builder.rust_version(), OverlayKind::LlvmBitcodeLinker => builder.rust_version(), OverlayKind::Gcc => builder.rust_version(), + OverlayKind::Enzyme => builder.rust_version(), } } } From f19591ad27ba306ffe0d2665790894420e0f96bc Mon Sep 17 00:00:00 2001 From: Redddy Date: Fri, 16 Jan 2026 18:39:38 +0900 Subject: [PATCH 08/27] Add rust-analyzer setup for out-of-tree rustc_private crates --- .../remarks-on-perma-unstable-features.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/remarks-on-perma-unstable-features.md b/src/doc/rustc-dev-guide/src/rustc-driver/remarks-on-perma-unstable-features.md index b434cfc9cf146..3c2fe133be84e 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/remarks-on-perma-unstable-features.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/remarks-on-perma-unstable-features.md @@ -49,6 +49,29 @@ For custom-built toolchains or environments not using rustup, additional configu ``` 3. **Check version compatibility**: Ensure your LLVM version is compatible with your Rust toolchain +### Configuring `rust-analyzer` for Out-of-Tree Projects + +When developing out-of-tree projects that use `rustc_private` crates, you can configure `rust-analyzer` to recognize these crates. + +#### Configuration Steps + +1. **Set rust-analyzer configuration** + Configure `rust-analyzer.rustc.source` to `"discover"` in your editor settings. + For VS Code, add to `rust_analyzer_settings.json`: + ```json + { + "rust-analyzer.rustc.source": "discover" + } + ``` +2. **Add metadata to Cargo.toml** + Add the following to the `Cargo.toml` of every crate that uses `rustc_private`: + ```toml + [package.metadata.rust-analyzer] + rustc_private = true + ``` + +This configuration allows `rust-analyzer` to properly recognize and provide IDE support for `rustc_private` crates in out-of-tree projects. + ### Additional Resources - [GitHub Issue #137421](https://github.com/rust-lang/rust/issues/137421): Explains that `rustc_private` linker failures often occur because `llvm-tools` is not installed From 21f269e72ed90f73abbd531243d6f563d2689650 Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 16 Jan 2026 10:47:01 +0100 Subject: [PATCH 09/27] add RPITIT cycle issue --- .../src/solve/candidate-preference.md | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) 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 8b28f56760a7a..d5a75b41ff1f0 100644 --- a/src/doc/rustc-dev-guide/src/solve/candidate-preference.md +++ b/src/doc/rustc-dev-guide/src/solve/candidate-preference.md @@ -260,11 +260,13 @@ We prefer builtin trait object impls over user-written impls. This is **unsound* The candidate preference behavior during normalization is implemented in [`fn assemble_and_merge_candidates`]. -### Where-bounds shadow impls +### Trait where-bounds shadow impls Normalization of associated items does not consider impls if the corresponding trait goal has been proven via a `ParamEnv` or `AliasBound` candidate. This means that for where-bounds which do not constrain associated types, the associated types remain *rigid*. +#### Using impls results in different region constraints + This is necessary to avoid unnecessary region constraints from applying impls. ```rust trait Trait<'a> { @@ -286,6 +288,39 @@ where } ``` +#### RPITIT `type_of` cycles + +We currently have to avoid impl candidates if there are where-bounds to avoid query cycles for RPITIT, see [#139762]. It feels desirable to me to stop relying on auto-trait leakage of during RPITIT computation to remove this issue, see [#139788]. + +```rust +use std::future::Future; +pub trait ReactiveFunction: Send { + type Output; + + fn invoke(self) -> Self::Output; +} + +trait AttributeValue { + fn resolve(self) -> impl Future + Send; +} + +impl AttributeValue for F +where + F: ReactiveFunction, + V: AttributeValue, +{ + async fn resolve(self) { + // We're awaiting `::{synthetic#0}` here. + // Normalizing that one via the the impl we're currently in + // relies on `collect_return_position_impl_trait_in_trait_tys` which + // ends up relying on auto-trait leakage when checking that the + // opaque return type of this function implements the `Send` item + // bound of the trait definition. + self.invoke().resolve().await + } +} +``` + ### We always consider `AliasBound` candidates In case the where-bound does not specify the associated item, we consider `AliasBound` candidates instead of treating the alias as rigid, even though the trait goal was proven via a `ParamEnv` candidate. @@ -424,4 +459,6 @@ where [`fn assemble_and_merge_candidates`]: https://github.com/rust-lang/rust/blob/e3ee7f7aea5b45af3b42b5e4713da43876a65ac9/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs#L920-L1003 [trait-system-refactor-initiative#76]: https://github.com/rust-lang/trait-system-refactor-initiative/issues/76 [#24066]: https://github.com/rust-lang/rust/issues/24066 -[#133044]: https://github.com/rust-lang/rust/issues/133044 \ No newline at end of file +[#133044]: https://github.com/rust-lang/rust/issues/133044 +[#139762]: https://github.com/rust-lang/rust/pull/139762 +[#139788]: https://github.com/rust-lang/rust/issues/139788 \ No newline at end of file From 908ae5a86f5191bf316456dcc9257a942d8bce7f Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 16 Jan 2026 10:48:01 +0100 Subject: [PATCH 10/27] move section --- .../src/solve/candidate-preference.md | 121 +++++++++--------- 1 file changed, 61 insertions(+), 60 deletions(-) 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 d5a75b41ff1f0..461523424b3c3 100644 --- a/src/doc/rustc-dev-guide/src/solve/candidate-preference.md +++ b/src/doc/rustc-dev-guide/src/solve/candidate-preference.md @@ -260,66 +260,6 @@ We prefer builtin trait object impls over user-written impls. This is **unsound* The candidate preference behavior during normalization is implemented in [`fn assemble_and_merge_candidates`]. -### Trait where-bounds shadow impls - -Normalization of associated items does not consider impls if the corresponding trait goal has been proven via a `ParamEnv` or `AliasBound` candidate. -This means that for where-bounds which do not constrain associated types, the associated types remain *rigid*. - -#### Using impls results in different region constraints - -This is necessary to avoid unnecessary region constraints from applying impls. -```rust -trait Trait<'a> { - type Assoc; -} -impl Trait<'static> for u32 { - type Assoc = u32; -} - -fn bar<'b, T: Trait<'b>>() -> T::Assoc { todo!() } -fn foo<'a>() -where - u32: Trait<'a>, -{ - // Normalizing the return type would use the impl, proving - // the `T: Trait` where-bound would use the where-bound, resulting - // in different region constraints. - bar::<'_, u32>(); -} -``` - -#### RPITIT `type_of` cycles - -We currently have to avoid impl candidates if there are where-bounds to avoid query cycles for RPITIT, see [#139762]. It feels desirable to me to stop relying on auto-trait leakage of during RPITIT computation to remove this issue, see [#139788]. - -```rust -use std::future::Future; -pub trait ReactiveFunction: Send { - type Output; - - fn invoke(self) -> Self::Output; -} - -trait AttributeValue { - fn resolve(self) -> impl Future + Send; -} - -impl AttributeValue for F -where - F: ReactiveFunction, - V: AttributeValue, -{ - async fn resolve(self) { - // We're awaiting `::{synthetic#0}` here. - // Normalizing that one via the the impl we're currently in - // relies on `collect_return_position_impl_trait_in_trait_tys` which - // ends up relying on auto-trait leakage when checking that the - // opaque return type of this function implements the `Send` item - // bound of the trait definition. - self.invoke().resolve().await - } -} -``` ### We always consider `AliasBound` candidates @@ -453,6 +393,67 @@ where } ``` +### Trait where-bounds shadow impls + +Normalization of associated items does not consider impls if the corresponding trait goal has been proven via a `ParamEnv` or `AliasBound` candidate. +This means that for where-bounds which do not constrain associated types, the associated types remain *rigid*. + +#### Using impls results in different region constraints + +This is necessary to avoid unnecessary region constraints from applying impls. +```rust +trait Trait<'a> { + type Assoc; +} +impl Trait<'static> for u32 { + type Assoc = u32; +} + +fn bar<'b, T: Trait<'b>>() -> T::Assoc { todo!() } +fn foo<'a>() +where + u32: Trait<'a>, +{ + // Normalizing the return type would use the impl, proving + // the `T: Trait` where-bound would use the where-bound, resulting + // in different region constraints. + bar::<'_, u32>(); +} +``` + +#### RPITIT `type_of` cycles + +We currently have to avoid impl candidates if there are where-bounds to avoid query cycles for RPITIT, see [#139762]. It feels desirable to me to stop relying on auto-trait leakage of during RPITIT computation to remove this issue, see [#139788]. + +```rust +use std::future::Future; +pub trait ReactiveFunction: Send { + type Output; + + fn invoke(self) -> Self::Output; +} + +trait AttributeValue { + fn resolve(self) -> impl Future + Send; +} + +impl AttributeValue for F +where + F: ReactiveFunction, + V: AttributeValue, +{ + async fn resolve(self) { + // We're awaiting `::{synthetic#0}` here. + // Normalizing that one via the the impl we're currently in + // relies on `collect_return_position_impl_trait_in_trait_tys` which + // ends up relying on auto-trait leakage when checking that the + // opaque return type of this function implements the `Send` item + // bound of the trait definition. + self.invoke().resolve().await + } +} +``` + [`Candidate`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_next_trait_solver/solve/assembly/struct.Candidate.html [`CandidateSource`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_next_trait_solver/solve/enum.CandidateSource.html [`fn merge_trait_candidates`]: https://github.com/rust-lang/rust/blob/e3ee7f7aea5b45af3b42b5e4713da43876a65ac9/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs#L1342-L1424 From 9e4c55ac8d9d1083983bdc2c35a3513cddb3fc69 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Fri, 16 Jan 2026 19:26:34 +0900 Subject: [PATCH 11/27] Rename "remarks on perma unstable features" to "external rustc_drivers" --- src/doc/rustc-dev-guide/src/SUMMARY.md | 2 +- ...-on-perma-unstable-features.md => external-rustc-drivers.md} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/doc/rustc-dev-guide/src/rustc-driver/{remarks-on-perma-unstable-features.md => external-rustc-drivers.md} (98%) diff --git a/src/doc/rustc-dev-guide/src/SUMMARY.md b/src/doc/rustc-dev-guide/src/SUMMARY.md index 2dd07144a78cb..daaaef42d9096 100644 --- a/src/doc/rustc-dev-guide/src/SUMMARY.md +++ b/src/doc/rustc-dev-guide/src/SUMMARY.md @@ -140,7 +140,7 @@ - [Command-line arguments](./cli.md) - [rustc_driver and rustc_interface](./rustc-driver/intro.md) - - [Remarks on perma-unstable features](./rustc-driver/remarks-on-perma-unstable-features.md) + - [External rustc_drivers](./rustc-driver/external-rustc-drivers.md) - [Example: Type checking](./rustc-driver/interacting-with-the-ast.md) - [Example: Getting diagnostics](./rustc-driver/getting-diagnostics.md) - [Errors and lints](diagnostics.md) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/remarks-on-perma-unstable-features.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md similarity index 98% rename from src/doc/rustc-dev-guide/src/rustc-driver/remarks-on-perma-unstable-features.md rename to src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index 3c2fe133be84e..1049d7a82dddb 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/remarks-on-perma-unstable-features.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -1,4 +1,4 @@ -# Remarks on perma unstable features +# External `rustc_driver`s ## `rustc_private` From 308b736541d4fdc000cf8abb1577f0d55e05c77c Mon Sep 17 00:00:00 2001 From: Redddy Date: Thu, 15 Jan 2026 23:56:14 +0900 Subject: [PATCH 12/27] Added section on using GitHub Dev for PR inspection --- src/doc/rustc-dev-guide/src/git.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/git.md b/src/doc/rustc-dev-guide/src/git.md index e7e84e2ea7f54..abe72b29cb1e2 100644 --- a/src/doc/rustc-dev-guide/src/git.md +++ b/src/doc/rustc-dev-guide/src/git.md @@ -495,6 +495,14 @@ command to check it out locally. See for more info. ![`gh` suggestion](./img/github-cli.png) +### Using GitHub dev + +As an alternative to the GitHub web UI, GitHub Dev provides a web-based editor for browsing +repository and PRs. It can be opened by replacing `github.com` with `github.dev` in the URL +or by pressing `.` on a GitHub page. +See [the docs for github.dev editor](https://docs.github.com/en/codespaces/the-githubdev-web-based-editor) +for more details. + ### Moving large sections of code Git and Github's default diff view for large moves *within* a file is quite poor; it will show each From b3a5f53e8f50e5c2532b52cb7dc9222692e739f4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 17 Jan 2026 17:02:46 +0100 Subject: [PATCH 13/27] rustdoc: Fix ICE when deprecated note is not resolved on the correct `DefId` --- .../passes/collect_intra_doc_links.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 018e20b76dc83..a68e9dc87ae52 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -11,10 +11,11 @@ use rustc_ast::util::comments::may_have_doc_links; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_errors::{Applicability, Diag, DiagMessage}; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def::Namespace::*; use rustc_hir::def::{DefKind, MacroKinds, Namespace, PerNS}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE}; -use rustc_hir::{Mutability, Safety}; +use rustc_hir::{Attribute, Mutability, Safety}; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_middle::{bug, span_bug, ty}; use rustc_resolve::rustdoc::pulldown_cmark::LinkType; @@ -1108,10 +1109,8 @@ impl LinkCollector<'_, '_> { // Also resolve links in the note text of `#[deprecated]`. for attr in &item.attrs.other_attrs { - let rustc_hir::Attribute::Parsed(rustc_hir::attrs::AttributeKind::Deprecation { - span, - deprecation, - }) = attr + let Attribute::Parsed(AttributeKind::Deprecation { span: depr_span, deprecation }) = + attr else { continue; }; @@ -1128,8 +1127,14 @@ impl LinkCollector<'_, '_> { // inlined item. // let item_id = if let Some(inline_stmt_id) = item.inline_stmt_id - && item.span(tcx).is_none_or(|item_span| !item_span.inner().contains(*span)) - { + && tcx.get_all_attrs(inline_stmt_id).iter().any(|attr| { + matches!( + attr, + Attribute::Parsed(AttributeKind::Deprecation { + span: attr_span, .. + }) if attr_span == depr_span, + ) + }) { inline_stmt_id.to_def_id() } else { item.item_id.expect_def_id() From 1faaa769615cda3c662f96b5f48678e6f97ee245 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 17 Jan 2026 17:03:04 +0100 Subject: [PATCH 14/27] Add regression test for ICE when deprecated note is not resolved on the correct `DefId` --- .../deprecated-note-from-reexported.rs | 16 +++++++++ .../deprecated-note-from-reexported.stderr | 34 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/rustdoc-ui/intra-doc/deprecated-note-from-reexported.rs create mode 100644 tests/rustdoc-ui/intra-doc/deprecated-note-from-reexported.stderr diff --git a/tests/rustdoc-ui/intra-doc/deprecated-note-from-reexported.rs b/tests/rustdoc-ui/intra-doc/deprecated-note-from-reexported.rs new file mode 100644 index 0000000000000..3d1e48a4c6382 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/deprecated-note-from-reexported.rs @@ -0,0 +1,16 @@ +// This test ensures that the intra-doc link from reexported deprecated attribute note +// are resolved where they are declared. + +#![deny(rustdoc::broken_intra_doc_links)] + +#[doc(inline)] +pub use bar::sql_function_proc as sql_function; + +pub fn define_sql_function() {} + +pub mod bar { + #[deprecated(note = "Use [`define_sql_function`] instead")] + //~^ ERROR: unresolved link + //~| ERROR: unresolved link + pub fn sql_function_proc() {} +} diff --git a/tests/rustdoc-ui/intra-doc/deprecated-note-from-reexported.stderr b/tests/rustdoc-ui/intra-doc/deprecated-note-from-reexported.stderr new file mode 100644 index 0000000000000..25f10b24d9fb5 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/deprecated-note-from-reexported.stderr @@ -0,0 +1,34 @@ +error: unresolved link to `define_sql_function` + --> $DIR/deprecated-note-from-reexported.rs:12:25 + | +LL | #[deprecated(note = "Use [`define_sql_function`] instead")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the link appears in this line: + + Use [`define_sql_function`] instead + ^^^^^^^^^^^^^^^^^^^^^ + = note: no item named `define_sql_function` in scope + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` +note: the lint level is defined here + --> $DIR/deprecated-note-from-reexported.rs:4:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unresolved link to `define_sql_function` + --> $DIR/deprecated-note-from-reexported.rs:12:25 + | +LL | #[deprecated(note = "Use [`define_sql_function`] instead")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the link appears in this line: + + Use [`define_sql_function`] instead + ^^^^^^^^^^^^^^^^^^^^^ + = note: no item named `define_sql_function` in scope + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 2 previous errors + From eed703ccc0d534f38e5d7eca30c6675364566b69 Mon Sep 17 00:00:00 2001 From: Oscar Bray Date: Sun, 18 Jan 2026 08:22:33 +0000 Subject: [PATCH 15/27] Fix typo. Match "build-rust-analyzer" in src/building/how-to-build-and-run.md and in the default editor settings from the rustc repo. --- src/doc/rustc-dev-guide/src/building/suggested.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index 1c75de3e90421..c87dc6b28d875 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -97,7 +97,7 @@ for two reasons: additional rebuilds in some cases. To avoid these problems: -- Add `--build-dir=build/rust-analyzer` to all of the custom `x` commands in +- Add `--build-dir=build-rust-analyzer` to all of the custom `x` commands in your editor's rust-analyzer configuration. (Feel free to choose a different directory name if desired.) - Modify the `rust-analyzer.rustfmt.overrideCommand` setting so that it points From b0958e58bf2f5a3a6ce7f433e6da7a84b20c08c2 Mon Sep 17 00:00:00 2001 From: Matthew Tejo Date: Mon, 19 Jan 2026 08:53:43 -0800 Subject: [PATCH 16/27] Fix issue query deadlink in getting-started.md The link for finding issues leads to a 404. This adds the repo to the query in the url so the search works. --- src/doc/rustc-dev-guide/src/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/getting-started.md b/src/doc/rustc-dev-guide/src/getting-started.md index 6ccc5a75497aa..a5da912aa4c4d 100644 --- a/src/doc/rustc-dev-guide/src/getting-started.md +++ b/src/doc/rustc-dev-guide/src/getting-started.md @@ -96,7 +96,7 @@ For example: Not all important or beginner work has issue labels. See below for how to find work that isn't labelled. -[help-wanted-search]: https://github.com/issues?q=is%3Aopen%20is%3Aissue%20org%3Arust-lang%20no%3Aassignee%20label%3AE-easy%2CE-medium%2CE-help-wanted%2CE-mentor%20-label%3AS-blocked%20-linked%3Apr +[help-wanted-search]: https://github.com/rust-lang/rust/issues?q=is%3Aopen%20is%3Aissue%20org%3Arust-lang%20no%3Aassignee%20label%3AE-easy%2CE-medium%2CE-help-wanted%2CE-mentor%20-label%3AS-blocked%20-linked%3Apr [Triage]: ./contributing.md#issue-triage ### Recurring work From 8a573580f842fd2ebc888439ceaff75aabc13b0c Mon Sep 17 00:00:00 2001 From: Boxy Date: Mon, 19 Jan 2026 16:46:12 +0000 Subject: [PATCH 17/27] Fix terminal dependent tests --- src/doc/rustc-dev-guide/src/tests/ui.md | 4 ++++ tests/ui/compiletest-self-test/ui-testing-optout.rs | 2 +- tests/ui/consts/missing_span_in_backtrace.rs | 2 +- tests/ui/consts/missing_span_in_backtrace.stderr | 12 ++++++------ .../ui/error-emitter/trimmed_multiline_suggestion.rs | 2 +- .../trimmed_multiline_suggestion.stderr | 2 +- tests/ui/modules/issue-107649.rs | 2 +- tests/ui/span/issue-71363.rs | 2 +- 8 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ui.md b/src/doc/rustc-dev-guide/src/tests/ui.md index 759029e671322..e13419d1e01cc 100644 --- a/src/doc/rustc-dev-guide/src/tests/ui.md +++ b/src/doc/rustc-dev-guide/src/tests/ui.md @@ -139,6 +139,10 @@ prefixing each source line are replaced with `LL`). In extremely rare situations, this mode can be disabled with the directive `//@ compile-flags: -Z ui-testing=no`. +When using `-Z ui-testing=no` the `--diagnostic-width` argument should also +be set to avoid tests failing or passing depending on the width of the terminal +from which the UI test suite is being run. + Note: The line and column numbers for `-->` lines pointing to the test are *not* normalized, and left as-is. This ensures that the compiler continues to point to the correct location, and keeps the stderr files readable. Ideally all diff --git a/tests/ui/compiletest-self-test/ui-testing-optout.rs b/tests/ui/compiletest-self-test/ui-testing-optout.rs index 62920a86c3b3e..2f2bc54676a8d 100644 --- a/tests/ui/compiletest-self-test/ui-testing-optout.rs +++ b/tests/ui/compiletest-self-test/ui-testing-optout.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z ui-testing=no +//@ compile-flags: -Z ui-testing=no --diagnostic-width=80 // Line number < 10 type A = B; //~ ERROR diff --git a/tests/ui/consts/missing_span_in_backtrace.rs b/tests/ui/consts/missing_span_in_backtrace.rs index 09a11c75fd7bd..b679493eb08eb 100644 --- a/tests/ui/consts/missing_span_in_backtrace.rs +++ b/tests/ui/consts/missing_span_in_backtrace.rs @@ -1,6 +1,6 @@ //! Check what happens when the error occurs inside a std function that we can't print the span of. //@ ignore-backends: gcc -//@ compile-flags: -Z ui-testing=no +//@ compile-flags: -Z ui-testing=no --diagnostic-width=80 use std::{ mem::{self, MaybeUninit}, diff --git a/tests/ui/consts/missing_span_in_backtrace.stderr b/tests/ui/consts/missing_span_in_backtrace.stderr index 55dde63582861..b86911e7057b2 100644 --- a/tests/ui/consts/missing_span_in_backtrace.stderr +++ b/tests/ui/consts/missing_span_in_backtrace.stderr @@ -1,12 +1,12 @@ error[E0080]: memory access failed: attempting to access 1 byte, but got ALLOC0+0x4 which is at or beyond the end of the allocation of size 4 bytes --> $DIR/missing_span_in_backtrace.rs:16:9 | -16 | / ptr::swap_nonoverlapping( -17 | | &mut x1 as *mut _ as *mut MaybeUninit, -18 | | &mut x2 as *mut _ as *mut MaybeUninit, -19 | | 10, -20 | | ); - | |_________^ evaluation of `X` failed inside this call +16 | / ... ptr::swap_nonoverlapping( +17 | | ... &mut x1 as *mut _ as *mut MaybeUninit, +18 | | ... &mut x2 as *mut _ as *mut MaybeUninit, +19 | | ... 10, +20 | | ... ); + | |_______^ evaluation of `X` failed inside this call | note: inside `swap_nonoverlapping::compiletime::>` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL diff --git a/tests/ui/error-emitter/trimmed_multiline_suggestion.rs b/tests/ui/error-emitter/trimmed_multiline_suggestion.rs index c0ae9af4e9b25..7100a96611c6f 100644 --- a/tests/ui/error-emitter/trimmed_multiline_suggestion.rs +++ b/tests/ui/error-emitter/trimmed_multiline_suggestion.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z ui-testing=no +//@ compile-flags: -Z ui-testing=no --diagnostic-width=80 fn function_with_lots_of_arguments(a: i32, b: char, c: i32, d: i32, e: i32, f: i32) {} fn main() { diff --git a/tests/ui/error-emitter/trimmed_multiline_suggestion.stderr b/tests/ui/error-emitter/trimmed_multiline_suggestion.stderr index 6e1ffb153bc2b..d593647a5ba07 100644 --- a/tests/ui/error-emitter/trimmed_multiline_suggestion.stderr +++ b/tests/ui/error-emitter/trimmed_multiline_suggestion.stderr @@ -10,7 +10,7 @@ error[E0061]: this function takes 6 arguments but 5 arguments were supplied note: function defined here --> $DIR/trimmed_multiline_suggestion.rs:2:4 | -2 | fn function_with_lots_of_arguments(a: i32, b: char, c: i32, d: i32, e: i32, f: i32) {} +2 | fn function_with_lots_of_arguments(a: i32, b: char, c: i32, d: i32, e: i3... | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ------- help: provide the argument | diff --git a/tests/ui/modules/issue-107649.rs b/tests/ui/modules/issue-107649.rs index f93fb07e17af0..731c2c0592a18 100644 --- a/tests/ui/modules/issue-107649.rs +++ b/tests/ui/modules/issue-107649.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z ui-testing=no +//@ compile-flags: -Z ui-testing=no --diagnostic-width=80 #[path = "auxiliary/dummy_lib.rs"] mod lib; diff --git a/tests/ui/span/issue-71363.rs b/tests/ui/span/issue-71363.rs index f186e0526a424..4ce8a942c7b1b 100644 --- a/tests/ui/span/issue-71363.rs +++ b/tests/ui/span/issue-71363.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z ui-testing=no +//@ compile-flags: -Z ui-testing=no --diagnostic-width=80 struct MyError; impl std::error::Error for MyError {} From 9f965bcf76f2fb5128b41e2e898e2d7ed537c72c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 14 Jan 2026 00:45:39 +0100 Subject: [PATCH 18/27] Add rustdoc GUI test to ensure that the "hide deprecated items" setting is working as expected --- .../rustdoc-gui/setting-hide-deprecated.goml | 83 +++++++++++++++++++ tests/rustdoc-gui/src/lib2/lib.rs | 43 ++++++++++ tests/rustdoc-gui/utils.goml | 12 ++- 3 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 tests/rustdoc-gui/setting-hide-deprecated.goml diff --git a/tests/rustdoc-gui/setting-hide-deprecated.goml b/tests/rustdoc-gui/setting-hide-deprecated.goml new file mode 100644 index 0000000000000..77f84493c67b5 --- /dev/null +++ b/tests/rustdoc-gui/setting-hide-deprecated.goml @@ -0,0 +1,83 @@ +// Test that the "hide deprecated" setting is correctly handled. + +include: "utils.goml" + +go-to: "file://" + |DOC_PATH| + "/lib2/deprecated/index.html" + +// There should be two deprecated items listed on the module page: +// `DeprecatedStruct` and `DeprecatedTrait`. +assert-count: ("dt.deprecated", 2) +// `DeprecatedStruct` and `DeprecatedTrait` should be displayed for now. +assert-css: ("dt.deprecated", {"display": "block"}, ALL) + +// We enable the "hide deprecated items" setting. +call-function: ("open-settings-menu", {}) +click: "#hide-deprecated-items" +// None of them should be displayed anymore. +wait-for-css: ("dt.deprecated", {"display": "none"}, ALL) + +// We disable the setting. +click: "#hide-deprecated-items" +// All of them should be displayed back. +wait-for-css: ("dt.deprecated", {"display": "block"}, ALL) + +// Now we go to a trait with a deprecated method and a deprecated associated const. +go-to: "file://" + |DOC_PATH| + "/lib2/deprecated/trait.NormalTrait.html" + +// There should be two deprecated items. +assert-count: ("details.deprecated", 2) +// They should be displayed for now. +assert-css: ("details.deprecated", {"display": "block"}, ALL) + +// We enable the "hide deprecated items" setting. +call-function: ("open-settings-menu", {}) +click: "#hide-deprecated-items" + +// They shouldn't be displayed anymore. +wait-for-css: ("details.deprecated", {"display": "none"}, ALL) + +// We disable the setting. +click: "#hide-deprecated-items" +// All of them should be displayed back. +wait-for-css: ("details.deprecated", {"display": "block"}, ALL) + +// We now go to a struct with a deprecated method which implements a deprecated trait and a trait +// with deprecated associated items. +go-to: "file://" + |DOC_PATH| + "/lib2/deprecated/struct.NonDeprecatedStruct.html" + +// There should be five deprecated items (six minus one "future" deprecated)... +assert-count: ("details.deprecated", 5) +// One of which being a deprecated impl because the trait itself is deprecated. +assert-count: ("details.implementors-toggle.deprecated", 1) +// And another has `since = "TBD"` and should NOT have the `deprecated` class. +assert: "details:not(.deprecated) #method\.future_deprecated_fn" +// They should all be displayed for now. +assert-css: ("details.deprecated", {"display": "block"}, ALL) + +// We enable the "hide deprecated items" setting. +call-function: ("open-settings-menu", {}) +click: "#hide-deprecated-items" + +// They shouldn't be displayed anymore. +wait-for-css: ("details.deprecated", {"display": "none"}, ALL) + +// We disable the setting. +click: "#hide-deprecated-items" +// All of them should be displayed back. +wait-for-css: ("details.deprecated", {"display": "block"}, ALL) + +// And now we check with the search results. +call-function: ("perform-search", {"query": "deprecated::depr"}) +// There should at least 7 results. +store-count: ("#results ul.search-results.active > a", nb_search_results) +assert: |nb_search_results| >= 7 +// There should be at least 5 deprecated items. +store-count: ("#results ul.search-results.active > a.deprecated", nb_deprecated_results) +assert: |nb_search_results| >= 5 +// Deprecated items should all be displayed. +assert-css: ("#results ul.search-results.active > a.deprecated", {"display": "grid"}, ALL) +// We enable the "hide deprecated items" setting. +call-function: ("open-settings-menu", {}) +click: "#hide-deprecated-items" +// None of them should be displayed anymore. +wait-for-css: ("#results ul.search-results.active > a.deprecated", {"display": "none"}, ALL) diff --git a/tests/rustdoc-gui/src/lib2/lib.rs b/tests/rustdoc-gui/src/lib2/lib.rs index b87fdeea89da0..1367b17b37b2f 100644 --- a/tests/rustdoc-gui/src/lib2/lib.rs +++ b/tests/rustdoc-gui/src/lib2/lib.rs @@ -368,3 +368,46 @@ impl std::ops::Deref for Derefer { &self.0 } } + +pub mod deprecated { + #[deprecated(since = "1.26.0", note = "deprecated")] + pub struct DeprecatedStruct; + + pub struct NonDeprecatedStruct; + + pub trait NormalTrait { + #[deprecated(since = "1.26.0", note = "deprecated")] + /// doc + const X: usize = 12; + + #[deprecated(since = "1.26.0", note = "deprecated")] + /// doc + fn normal_deprecated_fn(); + } + + #[deprecated(since = "1.26.0", note = "deprecated")] + pub trait DeprecatedTrait { + fn depr_deprecated_fn(); + } + + impl NonDeprecatedStruct { + #[deprecated(since = "1.26.0", note = "deprecated")] + /// doc + pub fn deprecated_fn() {} + + /// doc + pub fn non_deprecated_fn() {} + + #[deprecated(since = "TBD", note = "deprecated")] + /// doc + pub fn future_deprecated_fn() {} + } + + impl NormalTrait for NonDeprecatedStruct { + fn normal_deprecated_fn() {} + } + + impl DeprecatedTrait for NonDeprecatedStruct { + fn depr_deprecated_fn() {} + } +} diff --git a/tests/rustdoc-gui/utils.goml b/tests/rustdoc-gui/utils.goml index c0625ead2f1aa..29f1af293d6d9 100644 --- a/tests/rustdoc-gui/utils.goml +++ b/tests/rustdoc-gui/utils.goml @@ -60,8 +60,8 @@ define-function: ( ) define-function: ( - "perform-search", - [query], + "open-search", + [], block { // Block requests with doubled `//`. // Amazon S3 doesn't support them, but other web hosts do, @@ -72,6 +72,14 @@ define-function: ( // Perform search click: "#search-button" wait-for: ".search-input" + } +) + +define-function: ( + "perform-search", + [query], + block { + call-function: ("open-search", {}) write-into: (".search-input", |query|) press-key: 'Enter' // wait for the search to start From 5561167fbde6bc9f5968b093acd404ed1dbb499a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 14 Jan 2026 22:16:23 +0100 Subject: [PATCH 19/27] Improve JS source code for settings --- src/librustdoc/html/static/js/settings.js | 34 ++++------------------- src/librustdoc/html/static/js/storage.js | 30 ++++++++++---------- 2 files changed, 20 insertions(+), 44 deletions(-) diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index b28b76b0d2e78..94d71f9222d2f 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -43,27 +43,6 @@ } } break; - case "hide-sidebar": - if (value === true) { - addClass(document.documentElement, "hide-sidebar"); - } else { - removeClass(document.documentElement, "hide-sidebar"); - } - break; - case "hide-toc": - if (value === true) { - addClass(document.documentElement, "hide-toc"); - } else { - removeClass(document.documentElement, "hide-toc"); - } - break; - case "hide-modnav": - if (value === true) { - addClass(document.documentElement, "hide-modnav"); - } else { - removeClass(document.documentElement, "hide-modnav"); - } - break; case "sans-serif-fonts": if (value === true) { addClass(document.documentElement, "sans-serif"); @@ -71,18 +50,15 @@ removeClass(document.documentElement, "sans-serif"); } break; + case "hide-sidebar": + case "hide-toc": + case "hide-modnav": case "word-wrap-source-code": - if (value === true) { - addClass(document.documentElement, "word-wrap-source-code"); - } else { - removeClass(document.documentElement, "word-wrap-source-code"); - } - break; case "hide-deprecated-items": if (value === true) { - addClass(document.documentElement, "hide-deprecated-items"); + addClass(document.documentElement, settingName); } else { - removeClass(document.documentElement, "hide-deprecated-items"); + removeClass(document.documentElement, settingName); } } } diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 7cc70374378be..fc83578ddd83c 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -319,24 +319,24 @@ updateTheme(); if (getSettingValue("source-sidebar-show") === "true") { addClass(document.documentElement, "src-sidebar-expanded"); } -if (getSettingValue("hide-sidebar") === "true") { - addClass(document.documentElement, "hide-sidebar"); -} -if (getSettingValue("hide-toc") === "true") { - addClass(document.documentElement, "hide-toc"); -} -if (getSettingValue("hide-modnav") === "true") { - addClass(document.documentElement, "hide-modnav"); -} if (getSettingValue("sans-serif-fonts") === "true") { addClass(document.documentElement, "sans-serif"); } -if (getSettingValue("word-wrap-source-code") === "true") { - addClass(document.documentElement, "word-wrap-source-code"); -} -if (getSettingValue("hide-deprecated-items") === "true") { - addClass(document.documentElement, "hide-deprecated-items"); -} +(function() { + const settings = [ + "hide-sidebar", + "hide-toc", + "hide-modnav", + "word-wrap-source-code", + "hide-deprecated-items", + ]; + for (const setting of settings) { + if (getSettingValue(setting) === "true") { + addClass(document.documentElement, setting); + } + } +})(); + function updateSidebarWidth() { const desktopSidebarWidth = getSettingValue("desktop-sidebar-width"); if (desktopSidebarWidth && desktopSidebarWidth !== "null") { From bf49068b724fc2d8ae0984c5c8869857dffd3373 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 14 Jan 2026 22:20:31 +0100 Subject: [PATCH 20/27] Rename `sans-serif` CSS class into `sans-serif-fonts` to match the JS setting, allowing to simplify JS code --- src/librustdoc/html/static/css/rustdoc.css | 2 +- src/librustdoc/html/static/js/settings.js | 6 ------ src/librustdoc/html/static/js/storage.js | 4 +--- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 73eb260a2b8e2..ad7ef9b453cec 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -64,7 +64,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ '); } -:root.sans-serif { +:root.sans-serif-fonts { --font-family: "Fira Sans", sans-serif; --font-family-code: "Fira Mono", monospace; } diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 94d71f9222d2f..0bbeb6931e443 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -44,12 +44,6 @@ } break; case "sans-serif-fonts": - if (value === true) { - addClass(document.documentElement, "sans-serif"); - } else { - removeClass(document.documentElement, "sans-serif"); - } - break; case "hide-sidebar": case "hide-toc": case "hide-modnav": diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index fc83578ddd83c..1ec7cb4ced06e 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -319,9 +319,6 @@ updateTheme(); if (getSettingValue("source-sidebar-show") === "true") { addClass(document.documentElement, "src-sidebar-expanded"); } -if (getSettingValue("sans-serif-fonts") === "true") { - addClass(document.documentElement, "sans-serif"); -} (function() { const settings = [ "hide-sidebar", @@ -329,6 +326,7 @@ if (getSettingValue("sans-serif-fonts") === "true") { "hide-modnav", "word-wrap-source-code", "hide-deprecated-items", + "sans-serif-fonts", ]; for (const setting of settings) { if (getSettingValue(setting) === "true") { From 307a4fcdf803f7f1032bd317d8a34413d2d1e2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Dom=C3=ADnguez?= Date: Mon, 22 Dec 2025 21:03:02 +0100 Subject: [PATCH 21/27] Add scalar support for both host and device --- compiler/rustc_codegen_llvm/src/back/write.rs | 40 +++++++++++- compiler/rustc_codegen_llvm/src/builder.rs | 15 +++++ .../src/builder/gpu_offload.rs | 62 ++++++++++++++----- compiler/rustc_codegen_llvm/src/intrinsic.rs | 4 +- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 12 +++- .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 21 ++++++- compiler/rustc_middle/src/ty/offload_meta.rs | 17 +++-- .../codegen-llvm/gpu_offload/scalar_device.rs | 36 +++++++++++ tests/codegen-llvm/gpu_offload/scalar_host.rs | 37 +++++++++++ 9 files changed, 210 insertions(+), 34 deletions(-) create mode 100644 tests/codegen-llvm/gpu_offload/scalar_device.rs create mode 100644 tests/codegen-llvm/gpu_offload/scalar_host.rs diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index bcadb6f0de929..09d1ca1a59522 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -13,6 +13,7 @@ use rustc_codegen_ssa::back::write::{ TargetMachineFactoryConfig, TargetMachineFactoryFn, }; use rustc_codegen_ssa::base::wants_wasm_eh; +use rustc_codegen_ssa::common::TypeKind; use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind}; use rustc_data_structures::profiling::SelfProfilerRef; @@ -33,6 +34,8 @@ use crate::back::owned_target_machine::OwnedTargetMachine; use crate::back::profiling::{ LlvmSelfProfiler, selfprofile_after_pass_callback, selfprofile_before_pass_callback, }; +use crate::builder::SBuilder; +use crate::builder::gpu_offload::scalar_width; use crate::common::AsCCharPtr; use crate::errors::{ CopyBitcode, FromLlvmDiag, FromLlvmOptimizationDiag, LlvmError, UnknownCompression, @@ -669,7 +672,17 @@ pub(crate) unsafe fn llvm_optimize( // Create the new parameter list, with ptr as the first argument let mut new_param_types = Vec::with_capacity(old_param_count as usize + 1); new_param_types.push(cx.type_ptr()); - new_param_types.extend(old_param_types); + + // This relies on undocumented LLVM knowledge that scalars must be passed as i64 + for &old_ty in &old_param_types { + let new_ty = match cx.type_kind(old_ty) { + TypeKind::Half | TypeKind::Float | TypeKind::Double | TypeKind::Integer => { + cx.type_i64() + } + _ => old_ty, + }; + new_param_types.push(new_ty); + } // Create the new function type let ret_ty = unsafe { llvm::LLVMGetReturnType(old_fn_ty) }; @@ -682,10 +695,33 @@ pub(crate) unsafe fn llvm_optimize( let a0 = llvm::get_param(new_fn, 0); llvm::set_value_name(a0, CString::new("dyn_ptr").unwrap().as_bytes()); + let bb = SBuilder::append_block(cx, new_fn, "entry"); + let mut builder = SBuilder::build(cx, bb); + + let mut old_args_rebuilt = Vec::with_capacity(old_param_types.len()); + + for (i, &old_ty) in old_param_types.iter().enumerate() { + let new_arg = llvm::get_param(new_fn, (i + 1) as u32); + + let rebuilt = match cx.type_kind(old_ty) { + TypeKind::Half | TypeKind::Float | TypeKind::Double | TypeKind::Integer => { + let num_bits = scalar_width(cx, old_ty); + + let trunc = builder.trunc(new_arg, cx.type_ix(num_bits)); + builder.bitcast(trunc, old_ty) + } + _ => new_arg, + }; + + old_args_rebuilt.push(rebuilt); + } + + builder.ret_void(); + // Here we map the old arguments to the new arguments, with an offset of 1 to make sure // that we don't use the newly added `%dyn_ptr`. unsafe { - llvm::LLVMRustOffloadMapper(old_fn, new_fn); + llvm::LLVMRustOffloadMapper(old_fn, new_fn, old_args_rebuilt.as_ptr()); } llvm::set_linkage(new_fn, llvm::get_linkage(old_fn)); diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 7a49ba64029e5..557ae7b0333e3 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -97,6 +97,21 @@ impl<'a, 'll, CX: Borrow>> GenericBuilder<'a, 'll, CX> { GenericBuilder { llbuilder, cx: scx } } + pub(crate) fn append_block( + cx: &'a GenericCx<'ll, CX>, + llfn: &'ll Value, + name: &str, + ) -> &'ll BasicBlock { + unsafe { + let name = SmallCStr::new(name); + llvm::LLVMAppendBasicBlockInContext(cx.llcx(), llfn, name.as_ptr()) + } + } + + pub(crate) fn trunc(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value { + unsafe { llvm::LLVMBuildTrunc(self.llbuilder, val, dest_ty, UNNAMED) } + } + pub(crate) fn bitcast(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value { unsafe { llvm::LLVMBuildBitCast(self.llbuilder, val, dest_ty, UNNAMED) } } diff --git a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs index 7817755dafe41..c591c785cae31 100644 --- a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs +++ b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs @@ -2,6 +2,7 @@ use std::ffi::CString; use llvm::Linkage::*; use rustc_abi::Align; +use rustc_codegen_ssa::common::TypeKind; use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue}; use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods}; use rustc_middle::bug; @@ -357,7 +358,6 @@ pub(crate) fn add_global<'ll>( pub(crate) fn gen_define_handling<'ll>( cx: &CodegenCx<'ll, '_>, metadata: &[OffloadMetadata], - types: &[&'ll Type], symbol: String, offload_globals: &OffloadGlobals<'ll>, ) -> OffloadKernelGlobals<'ll> { @@ -367,25 +367,18 @@ pub(crate) fn gen_define_handling<'ll>( let offload_entry_ty = offload_globals.offload_entry_ty; - // It seems like non-pointer values are automatically mapped. So here, we focus on pointer (or - // reference) types. - let ptr_meta = types.iter().zip(metadata).filter_map(|(&x, meta)| match cx.type_kind(x) { - rustc_codegen_ssa::common::TypeKind::Pointer => Some(meta), - _ => None, - }); - // FIXME(Sa4dUs): add `OMP_MAP_TARGET_PARAM = 0x20` only if necessary - let (ptr_sizes, ptr_transfer): (Vec<_>, Vec<_>) = - ptr_meta.map(|m| (m.payload_size, m.mode.bits() | 0x20)).unzip(); + let (sizes, transfer): (Vec<_>, Vec<_>) = + metadata.iter().map(|m| (m.payload_size, m.mode.bits() | 0x20)).unzip(); - let offload_sizes = add_priv_unnamed_arr(&cx, &format!(".offload_sizes.{symbol}"), &ptr_sizes); + let offload_sizes = add_priv_unnamed_arr(&cx, &format!(".offload_sizes.{symbol}"), &sizes); // Here we figure out whether something needs to be copied to the gpu (=1), from the gpu (=2), // or both to and from the gpu (=3). Other values shouldn't affect us for now. // A non-mutable reference or pointer will be 1, an array that's not read, but fully overwritten // will be 2. For now, everything is 3, until we have our frontend set up. // 1+2+32: 1 (MapTo), 2 (MapFrom), 32 (Add one extra input ptr per function, to be used later). let memtransfer_types = - add_priv_unnamed_arr(&cx, &format!(".offload_maptypes.{symbol}"), &ptr_transfer); + add_priv_unnamed_arr(&cx, &format!(".offload_maptypes.{symbol}"), &transfer); // Next: For each function, generate these three entries. A weak constant, // the llvm.rodata entry name, and the llvm_offload_entries value @@ -441,11 +434,24 @@ fn declare_offload_fn<'ll>( ) } +pub(crate) fn scalar_width<'ll>(cx: &'ll SimpleCx<'_>, ty: &'ll Type) -> u64 { + match cx.type_kind(ty) { + TypeKind::Half + | TypeKind::Float + | TypeKind::Double + | TypeKind::X86_FP80 + | TypeKind::FP128 + | TypeKind::PPC_FP128 => cx.float_width(ty) as u64, + TypeKind::Integer => cx.int_width(ty), + other => bug!("scalar_width was called on a non scalar type {other:?}"), + } +} + // For each kernel *call*, we now use some of our previous declared globals to move data to and from // the gpu. For now, we only handle the data transfer part of it. // If two consecutive kernels use the same memory, we still move it to the host and back to the gpu. // Since in our frontend users (by default) don't have to specify data transfer, this is something -// we should optimize in the future! In some cases we can directly zero-allocate ont he device and +// we should optimize in the future! In some cases we can directly zero-allocate on the device and // only move data back, or if something is immutable, we might only copy it to the device. // // Current steps: @@ -533,8 +539,34 @@ pub(crate) fn gen_call_handling<'ll, 'tcx>( let mut geps = vec![]; let i32_0 = cx.get_const_i32(0); for &v in args { - let gep = builder.inbounds_gep(cx.type_f32(), v, &[i32_0]); - vals.push(v); + let ty = cx.val_ty(v); + let ty_kind = cx.type_kind(ty); + let (base_val, gep_base) = match ty_kind { + TypeKind::Pointer => (v, v), + TypeKind::Half | TypeKind::Float | TypeKind::Double | TypeKind::Integer => { + // FIXME(Sa4dUs): check for `f128` support, latest NVIDIA cards support it + let num_bits = scalar_width(cx, ty); + + let bb = builder.llbb(); + unsafe { + llvm::LLVMRustPositionBuilderPastAllocas(builder.llbuilder, builder.llfn()); + } + let addr = builder.direct_alloca(cx.type_i64(), Align::EIGHT, "addr"); + unsafe { + llvm::LLVMPositionBuilderAtEnd(builder.llbuilder, bb); + } + + let cast = builder.bitcast(v, cx.type_ix(num_bits)); + let value = builder.zext(cast, cx.type_i64()); + builder.store(value, addr, Align::EIGHT); + (value, addr) + } + other => bug!("offload does not support {other:?}"), + }; + + let gep = builder.inbounds_gep(cx.type_f32(), gep_base, &[i32_0]); + + vals.push(base_val); geps.push(gep); } diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 59cbcd78dd0f9..c2975df4b6a00 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1388,7 +1388,7 @@ fn codegen_offload<'ll, 'tcx>( let args = get_args_from_tuple(bx, args[3], fn_target); let target_symbol = symbol_name_for_instance_in_crate(tcx, fn_target, LOCAL_CRATE); - let sig = tcx.fn_sig(fn_target.def_id()).instantiate_identity(); + let sig = tcx.fn_sig(fn_target.def_id()).skip_binder(); let sig = tcx.instantiate_bound_regions_with_erased(sig); let inputs = sig.inputs(); @@ -1404,7 +1404,7 @@ fn codegen_offload<'ll, 'tcx>( return; } }; - let offload_data = gen_define_handling(&cx, &metadata, &types, target_symbol, offload_globals); + let offload_data = gen_define_handling(&cx, &metadata, target_symbol, offload_globals); gen_call_handling(bx, &offload_data, &args, &types, &metadata, offload_globals, &offload_dims); } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index a90013801c8c0..c535fade9c040 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1675,7 +1675,11 @@ mod Offload { _M: &'a Module, _host_out: *const c_char, ) -> bool; - pub(crate) fn LLVMRustOffloadMapper<'a>(OldFn: &'a Value, NewFn: &'a Value); + pub(crate) fn LLVMRustOffloadMapper<'a>( + OldFn: &'a Value, + NewFn: &'a Value, + RebuiltArgs: *const &Value, + ); } } @@ -1702,7 +1706,11 @@ mod Offload_fallback { unimplemented!("This rustc version was not built with LLVM Offload support!"); } #[allow(unused_unsafe)] - pub(crate) unsafe fn LLVMRustOffloadMapper<'a>(_OldFn: &'a Value, _NewFn: &'a Value) { + pub(crate) unsafe fn LLVMRustOffloadMapper<'a>( + _OldFn: &'a Value, + _NewFn: &'a Value, + _RebuiltArgs: *const &Value, + ) { unimplemented!("This rustc version was not built with LLVM Offload support!"); } } diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 38e8886910f76..336d589740362 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -223,7 +223,12 @@ extern "C" bool LLVMRustOffloadEmbedBufferInModule(LLVMModuleRef HostM, return true; } -extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn) { +// Clone OldFn into NewFn, remapping its arguments to RebuiltArgs. +// Each arg of OldFn is replaced with the corresponding value in RebuiltArgs. +// For scalars, RebuiltArgs contains the value cast and/or truncated to the +// original type. +extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn, + const LLVMValueRef *RebuiltArgs) { llvm::Function *oldFn = llvm::unwrap(OldFn); llvm::Function *newFn = llvm::unwrap(NewFn); @@ -232,15 +237,25 @@ extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn) { llvm::ValueToValueMapTy vmap; auto newArgIt = newFn->arg_begin(); newArgIt->setName("dyn_ptr"); - ++newArgIt; // skip %dyn_ptr + + unsigned i = 0; for (auto &oldArg : oldFn->args()) { - vmap[&oldArg] = &*newArgIt++; + vmap[&oldArg] = unwrap(RebuiltArgs[i++]); } llvm::SmallVector returns; llvm::CloneFunctionInto(newFn, oldFn, vmap, llvm::CloneFunctionChangeType::LocalChangesOnly, returns); + + BasicBlock &entry = newFn->getEntryBlock(); + BasicBlock &clonedEntry = *std::next(newFn->begin()); + + if (entry.getTerminator()) + entry.getTerminator()->eraseFromParent(); + + IRBuilder<> B(&entry); + B.CreateBr(&clonedEntry); } #endif diff --git a/compiler/rustc_middle/src/ty/offload_meta.rs b/compiler/rustc_middle/src/ty/offload_meta.rs index 04a7cd2c75f28..67c00765ed57b 100644 --- a/compiler/rustc_middle/src/ty/offload_meta.rs +++ b/compiler/rustc_middle/src/ty/offload_meta.rs @@ -78,16 +78,13 @@ impl MappingFlags { use rustc_ast::Mutability::*; match ty.kind() { - ty::Bool - | ty::Char - | ty::Int(_) - | ty::Uint(_) - | ty::Float(_) - | ty::Adt(_, _) - | ty::Tuple(_) - | ty::Array(_, _) - | ty::Alias(_, _) - | ty::Param(_) => MappingFlags::TO, + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) => { + MappingFlags::LITERAL | MappingFlags::IMPLICIT + } + + ty::Adt(_, _) | ty::Tuple(_) | ty::Array(_, _) | ty::Alias(_, _) | ty::Param(_) => { + MappingFlags::TO + } ty::RawPtr(_, Not) | ty::Ref(_, _, Not) => MappingFlags::TO, diff --git a/tests/codegen-llvm/gpu_offload/scalar_device.rs b/tests/codegen-llvm/gpu_offload/scalar_device.rs new file mode 100644 index 0000000000000..61772d4040636 --- /dev/null +++ b/tests/codegen-llvm/gpu_offload/scalar_device.rs @@ -0,0 +1,36 @@ +//@ add-minicore +//@ revisions: amdgpu nvptx +//@[nvptx] compile-flags: -Copt-level=0 -Zunstable-options -Zoffload=Device --target nvptx64-nvidia-cuda --crate-type=rlib +//@[nvptx] needs-llvm-components: nvptx +//@[amdgpu] compile-flags: -Copt-level=0 -Zunstable-options -Zoffload=Device --target amdgcn-amd-amdhsa -Ctarget-cpu=gfx900 --crate-type=rlib +//@[amdgpu] needs-llvm-components: amdgpu +//@ no-prefer-dynamic +//@ needs-offload + +// This test verifies that the offload intrinsic is properly handling scalar args on the device, +// replacing the args by i64 and then trunc and cast them to the original type + +#![feature(abi_gpu_kernel, rustc_attrs, no_core)] +#![no_core] + +extern crate minicore; + +// CHECK: ; Function Attrs +// nvptx-NEXT: define ptx_kernel void @foo(ptr %dyn_ptr, ptr %0, i64 %1) +// amdgpu-NEXT: define amdgpu_kernel void @foo(ptr %dyn_ptr, ptr %0, i64 %1) +// CHECK-NEXT: entry: +// CHECK-NEXT: %2 = trunc i64 %1 to i32 +// CHECK-NEXT: %3 = bitcast i32 %2 to float +// CHECK-NEXT: br label %start +// CHECK: start: +// CHECK-NEXT: store float %3, ptr %0, align 4 +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +#[unsafe(no_mangle)] +#[rustc_offload_kernel] +pub unsafe extern "gpu-kernel" fn foo(x: *mut f32, k: f32) { + unsafe { + *x = k; + }; +} diff --git a/tests/codegen-llvm/gpu_offload/scalar_host.rs b/tests/codegen-llvm/gpu_offload/scalar_host.rs new file mode 100644 index 0000000000000..8c7dcd4dd5817 --- /dev/null +++ b/tests/codegen-llvm/gpu_offload/scalar_host.rs @@ -0,0 +1,37 @@ +//@ compile-flags: -Zoffload=Test -Zunstable-options -C opt-level=1 -Clto=fat +//@ no-prefer-dynamic +//@ needs-offload + +// This test verifies that the offload intrinsic is properly handling scalar args, passing them to +// the kernel as i64 + +#![feature(abi_gpu_kernel)] +#![feature(rustc_attrs)] +#![feature(core_intrinsics)] +#![no_main] + +// CHECK: define{{( dso_local)?}} void @main() +// CHECK-NOT: define +// CHECK: %addr = alloca i64, align 8 +// CHECK: store double 4.200000e+01, ptr %0, align 8 +// CHECK: %_0.i = load double, ptr %0, align 8 +// CHECK: store double %_0.i, ptr %addr, align 8 +// CHECK: %1 = getelementptr inbounds nuw i8, ptr %.offload_baseptrs, i64 8 +// CHECK-NEXT: store double %_0.i, ptr %1, align 8 +// CHECK-NEXT: %2 = getelementptr inbounds nuw i8, ptr %.offload_ptrs, i64 8 +// CHECK-NEXT: store ptr %addr, ptr %2, align 8 +// CHECK-NEXT: %3 = getelementptr inbounds nuw i8, ptr %.offload_sizes, i64 8 +// CHECK-NEXT: store i64 4, ptr %3, align 8 +// CHECK-NEXT: call void @__tgt_target_data_begin_mapper + +#[unsafe(no_mangle)] +fn main() { + let mut x = 0.0; + let k = core::hint::black_box(42.0); + + core::intrinsics::offload::<_, _, ()>(foo, [1, 1, 1], [1, 1, 1], (&mut x, k)); +} + +unsafe extern "C" { + pub fn foo(x: *mut f32, k: f32); +} From 60e488ec07c18a5ef314c46bba0d61bd61579ffe Mon Sep 17 00:00:00 2001 From: cyrgani Date: Mon, 19 Jan 2026 21:41:02 +0000 Subject: [PATCH 22/27] add basic `TokenStream` api tests --- .../auxiliary/api/proc_macro_api_tests.rs | 2 ++ .../proc-macro/auxiliary/api/tokenstream.rs | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/ui/proc-macro/auxiliary/api/tokenstream.rs diff --git a/tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs b/tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs index 4083604e18855..d778e46da6b99 100644 --- a/tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs +++ b/tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs @@ -8,6 +8,7 @@ extern crate proc_macro; mod cmp; mod ident; mod literal; +mod tokenstream; use proc_macro::TokenStream; @@ -18,6 +19,7 @@ pub fn run(input: TokenStream) -> TokenStream { cmp::test(); ident::test(); literal::test(); + tokenstream::test(); TokenStream::new() } diff --git a/tests/ui/proc-macro/auxiliary/api/tokenstream.rs b/tests/ui/proc-macro/auxiliary/api/tokenstream.rs new file mode 100644 index 0000000000000..7078f4413051f --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/api/tokenstream.rs @@ -0,0 +1,28 @@ +use proc_macro::*; + +fn assert_eq(l: TokenStream, r: TokenStream) { + assert_eq!(l.to_string(), r.to_string()); + for (lt, rt) in l.into_iter().zip(r) { + assert_eq!(lt.to_string(), rt.to_string()); + } +} + +pub fn test() { + assert_eq(TokenStream::new(), TokenStream::new()); + let mut stream = TokenStream::new(); + assert!(stream.is_empty()); + stream.extend(TokenStream::new()); + assert_eq(stream.clone(), TokenStream::new()); + + let old = stream.clone(); + stream.extend(vec![TokenTree::Ident(Ident::new("foo", Span::call_site()))]); + assert!(!stream.is_empty()); + assert!(old.is_empty()); + + let stream2 = stream + .clone() + .into_iter() + .inspect(|tree| assert_eq!(tree.to_string(), "foo")) + .collect::(); + assert_eq(stream.clone(), stream2); +} From 9e5e4326624d6da2c0412ec48c972de3e4c7d2f8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 19 Jan 2026 23:10:20 +0100 Subject: [PATCH 23/27] Simplify `IndexItem::deprecation` field: since we only need a boolean value in the end, no need to keep it around. Also: takes into account "is_in_effect" --- src/librustdoc/formats/cache.rs | 4 ++-- src/librustdoc/html/render/mod.rs | 2 +- src/librustdoc/html/render/search_index.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 56f10b03d62de..64e361d566c32 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -593,7 +593,7 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It cache, ); let aliases = item.attrs.get_doc_aliases(); - let deprecation = item.deprecation(tcx); + let is_deprecated = item.is_deprecated(tcx); let index_item = IndexItem { ty: item.type_(), defid: Some(defid), @@ -608,7 +608,7 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It impl_id, search_type, aliases, - deprecation, + is_deprecated, }; cache.search_index.push(index_item); diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 106aefa4626b9..46a60846f141a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -141,7 +141,7 @@ pub(crate) struct IndexItem { pub(crate) impl_id: Option, pub(crate) search_type: Option, pub(crate) aliases: Box<[Symbol]>, - pub(crate) deprecation: Option, + pub(crate) is_deprecated: bool, } /// A type used for the search index. diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 12b207dda5693..30b534003da17 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -1282,7 +1282,7 @@ pub(crate) fn build_index( cache, ), aliases: item.attrs.get_doc_aliases(), - deprecation: item.deprecation(tcx), + is_deprecated: item.is_deprecated(tcx), }); } } @@ -1519,7 +1519,7 @@ pub(crate) fn build_index( trait_parent: item.trait_parent_idx, module_path, exact_module_path, - deprecated: item.deprecation.is_some(), + deprecated: item.is_deprecated, associated_item_disambiguator: if let Some(impl_id) = item.impl_id && let Some(parent_idx) = item.parent_idx && associated_item_duplicates From aef8112b26099c1f68b227a25792cde689828f94 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 19 Jan 2026 23:14:09 +0100 Subject: [PATCH 24/27] Add test for search results of not yet deprecated search items Improve the `perform-search` goml function to work when search is already open --- .../rustdoc-gui/setting-hide-deprecated.goml | 47 ++++++++++++------- tests/rustdoc-gui/utils.goml | 31 ++++++++---- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/tests/rustdoc-gui/setting-hide-deprecated.goml b/tests/rustdoc-gui/setting-hide-deprecated.goml index 77f84493c67b5..6dc5a6bff175d 100644 --- a/tests/rustdoc-gui/setting-hide-deprecated.goml +++ b/tests/rustdoc-gui/setting-hide-deprecated.goml @@ -3,68 +3,69 @@ include: "utils.goml" go-to: "file://" + |DOC_PATH| + "/lib2/deprecated/index.html" +store-value: (deprecated_class, ".deprecated") // There should be two deprecated items listed on the module page: // `DeprecatedStruct` and `DeprecatedTrait`. -assert-count: ("dt.deprecated", 2) +assert-count: ("dt" + |deprecated_class|, 2) // `DeprecatedStruct` and `DeprecatedTrait` should be displayed for now. -assert-css: ("dt.deprecated", {"display": "block"}, ALL) +assert-css: ("dt" + |deprecated_class|, {"display": "block"}, ALL) // We enable the "hide deprecated items" setting. call-function: ("open-settings-menu", {}) click: "#hide-deprecated-items" // None of them should be displayed anymore. -wait-for-css: ("dt.deprecated", {"display": "none"}, ALL) +wait-for-css: ("dt" + |deprecated_class|, {"display": "none"}, ALL) // We disable the setting. click: "#hide-deprecated-items" // All of them should be displayed back. -wait-for-css: ("dt.deprecated", {"display": "block"}, ALL) +wait-for-css: ("dt" + |deprecated_class|, {"display": "block"}, ALL) // Now we go to a trait with a deprecated method and a deprecated associated const. go-to: "file://" + |DOC_PATH| + "/lib2/deprecated/trait.NormalTrait.html" // There should be two deprecated items. -assert-count: ("details.deprecated", 2) +assert-count: ("details" + |deprecated_class|, 2) // They should be displayed for now. -assert-css: ("details.deprecated", {"display": "block"}, ALL) +assert-css: ("details" + |deprecated_class|, {"display": "block"}, ALL) // We enable the "hide deprecated items" setting. call-function: ("open-settings-menu", {}) click: "#hide-deprecated-items" // They shouldn't be displayed anymore. -wait-for-css: ("details.deprecated", {"display": "none"}, ALL) +wait-for-css: ("details" + |deprecated_class|, {"display": "none"}, ALL) // We disable the setting. click: "#hide-deprecated-items" // All of them should be displayed back. -wait-for-css: ("details.deprecated", {"display": "block"}, ALL) +wait-for-css: ("details" + |deprecated_class|, {"display": "block"}, ALL) // We now go to a struct with a deprecated method which implements a deprecated trait and a trait // with deprecated associated items. go-to: "file://" + |DOC_PATH| + "/lib2/deprecated/struct.NonDeprecatedStruct.html" // There should be five deprecated items (six minus one "future" deprecated)... -assert-count: ("details.deprecated", 5) +assert-count: ("details" + |deprecated_class|, 5) // One of which being a deprecated impl because the trait itself is deprecated. -assert-count: ("details.implementors-toggle.deprecated", 1) +assert-count: ("details.implementors-toggle" + |deprecated_class|, 1) // And another has `since = "TBD"` and should NOT have the `deprecated` class. -assert: "details:not(.deprecated) #method\.future_deprecated_fn" +assert: "details:not(" + |deprecated_class| + ") #method\.future_deprecated_fn" // They should all be displayed for now. -assert-css: ("details.deprecated", {"display": "block"}, ALL) +assert-css: ("details" + |deprecated_class|, {"display": "block"}, ALL) // We enable the "hide deprecated items" setting. call-function: ("open-settings-menu", {}) click: "#hide-deprecated-items" // They shouldn't be displayed anymore. -wait-for-css: ("details.deprecated", {"display": "none"}, ALL) +wait-for-css: ("details" + |deprecated_class|, {"display": "none"}, ALL) // We disable the setting. click: "#hide-deprecated-items" // All of them should be displayed back. -wait-for-css: ("details.deprecated", {"display": "block"}, ALL) +wait-for-css: ("details" + |deprecated_class|, {"display": "block"}, ALL) // And now we check with the search results. call-function: ("perform-search", {"query": "deprecated::depr"}) @@ -72,12 +73,24 @@ call-function: ("perform-search", {"query": "deprecated::depr"}) store-count: ("#results ul.search-results.active > a", nb_search_results) assert: |nb_search_results| >= 7 // There should be at least 5 deprecated items. -store-count: ("#results ul.search-results.active > a.deprecated", nb_deprecated_results) +store-count: ("#results ul.search-results.active > a" + |deprecated_class|, nb_deprecated_results) assert: |nb_search_results| >= 5 // Deprecated items should all be displayed. -assert-css: ("#results ul.search-results.active > a.deprecated", {"display": "grid"}, ALL) +assert-css: ("#results ul.search-results.active > a" + |deprecated_class|, {"display": "grid"}, ALL) // We enable the "hide deprecated items" setting. call-function: ("open-settings-menu", {}) click: "#hide-deprecated-items" // None of them should be displayed anymore. -wait-for-css: ("#results ul.search-results.active > a.deprecated", {"display": "none"}, ALL) +wait-for-css: ( + "#results ul.search-results.active > a" + |deprecated_class|, + {"display": "none"}, + ALL, +) + +// Finally we check that the future deprecated item doesn't have the deprecated class in the search +// and therefore isn't impact by the setting. +call-function: ("perform-search", {"query": "deprecated::future_deprecated"}) +assert-text: ( + "#results ul.search-results.active > a:not(" + |deprecated_class| + ") .path", + " lib2::deprecated::NonDeprecatedStruct::future_deprecated_fn", +) diff --git a/tests/rustdoc-gui/utils.goml b/tests/rustdoc-gui/utils.goml index 29f1af293d6d9..3d9bdd1ac1498 100644 --- a/tests/rustdoc-gui/utils.goml +++ b/tests/rustdoc-gui/utils.goml @@ -63,15 +63,25 @@ define-function: ( "open-search", [], block { - // Block requests with doubled `//`. - // Amazon S3 doesn't support them, but other web hosts do, - // and so do file:/// URLs, which means we need to block - // it here if we want to avoid breaking the main docs site. - // https://github.com/rust-lang/rust/issues/145646 - block-network-request: "file://*//*" - // Perform search - click: "#search-button" - wait-for: ".search-input" + store-count: (".search-input", __search_input_count) + if: (|__search_input_count| != 0, block { + store-css: ("#alternative-display", {"display": __search_input_display}) + }) + else: block { + // Block requests with doubled `//`. + // Amazon S3 doesn't support them, but other web hosts do, + // and so do file:/// URLs, which means we need to block + // it here if we want to avoid breaking the main docs site. + // https://github.com/rust-lang/rust/issues/145646 + block-network-request: "file://*//*" + store-value: (__search_input_display, "none") + } + + if: (|__search_input_display| == "none", block { + // Open search + click: "#search-button" + wait-for: ".search-input" + }) } ) @@ -80,6 +90,9 @@ define-function: ( [query], block { call-function: ("open-search", {}) + // We empty the search input in case it wasn't empty. + set-property: (".search-input", {"value": ""}) + // We write the actual query. write-into: (".search-input", |query|) press-key: 'Enter' // wait for the search to start From 1f423a6e42cc2eb868fefbab1b3d81e152b34186 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 2 Dec 2025 20:49:26 +1100 Subject: [PATCH 25/27] coverage: Add a test for macros that only contain other macros --- tests/coverage/macros/pass-through.cov-map | 57 +++++++++++++++++ tests/coverage/macros/pass-through.coverage | 71 +++++++++++++++++++++ tests/coverage/macros/pass-through.rs | 70 ++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 tests/coverage/macros/pass-through.cov-map create mode 100644 tests/coverage/macros/pass-through.coverage create mode 100644 tests/coverage/macros/pass-through.rs diff --git a/tests/coverage/macros/pass-through.cov-map b/tests/coverage/macros/pass-through.cov-map new file mode 100644 index 0000000000000..83937e4fb8b76 --- /dev/null +++ b/tests/coverage/macros/pass-through.cov-map @@ -0,0 +1,57 @@ +Function name: pass_through::uses_inner_macro +Raw bytes (51): 0x[01, 01, 01, 01, 05, 09, 01, 24, 01, 00, 16, 01, 01, 08, 00, 1d, 05, 01, 09, 00, 0c, 05, 00, 0d, 00, 21, 05, 01, 09, 00, 15, 05, 01, 09, 00, 0c, 05, 00, 0d, 00, 20, 02, 01, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => $DIR/pass-through.rs +Number of expressions: 1 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +Number of file 0 mappings: 9 +- Code(Counter(0)) at (prev + 36, 1) to (start + 0, 22) +- Code(Counter(0)) at (prev + 1, 8) to (start + 0, 29) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 12) +- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 33) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 21) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 12) +- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 32) +- Code(Expression(0, Sub)) at (prev + 1, 5) to (start + 0, 6) + = (c0 - c1) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c1 + +Function name: pass_through::uses_middle_macro +Raw bytes (51): 0x[01, 01, 01, 01, 05, 09, 01, 2c, 01, 00, 17, 01, 01, 08, 00, 1d, 05, 01, 09, 00, 0c, 05, 00, 0d, 00, 22, 05, 01, 09, 00, 16, 05, 01, 09, 00, 0c, 05, 00, 0d, 00, 21, 02, 01, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => $DIR/pass-through.rs +Number of expressions: 1 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +Number of file 0 mappings: 9 +- Code(Counter(0)) at (prev + 44, 1) to (start + 0, 23) +- Code(Counter(0)) at (prev + 1, 8) to (start + 0, 29) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 12) +- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 34) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 22) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 12) +- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 33) +- Code(Expression(0, Sub)) at (prev + 1, 5) to (start + 0, 6) + = (c0 - c1) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c1 + +Function name: pass_through::uses_outer_macro +Raw bytes (51): 0x[01, 01, 01, 01, 05, 09, 01, 34, 01, 00, 16, 01, 01, 08, 00, 1d, 05, 01, 09, 00, 0c, 05, 00, 0d, 00, 21, 05, 01, 09, 00, 15, 05, 01, 09, 00, 0c, 05, 00, 0d, 00, 20, 02, 01, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => $DIR/pass-through.rs +Number of expressions: 1 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +Number of file 0 mappings: 9 +- Code(Counter(0)) at (prev + 52, 1) to (start + 0, 22) +- Code(Counter(0)) at (prev + 1, 8) to (start + 0, 29) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 12) +- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 33) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 21) +- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 12) +- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 32) +- Code(Expression(0, Sub)) at (prev + 1, 5) to (start + 0, 6) + = (c0 - c1) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c1 + diff --git a/tests/coverage/macros/pass-through.coverage b/tests/coverage/macros/pass-through.coverage new file mode 100644 index 0000000000000..f1adb235f74a3 --- /dev/null +++ b/tests/coverage/macros/pass-through.coverage @@ -0,0 +1,71 @@ + LL| |#![feature(coverage_attribute)] + LL| |//@ edition: 2024 + LL| | + LL| |// Test that when a macro expands to another macro, without any significant + LL| |// spans of its own, that this doesn't cause coverage instrumentation to give + LL| |// up and ignore the inner spans. + LL| | + LL| |macro_rules! inner_macro { + LL| | () => { + LL| | if core::hint::black_box(true) { + LL| | say("true"); + LL| | } else { + LL| | say("false"); + LL| | } + LL| | }; + LL| |} + LL| | + LL| |macro_rules! middle_macro { + LL| | () => { + LL| | inner_macro!() + LL| | }; + LL| |} + LL| | + LL| |macro_rules! outer_macro { + LL| | () => { + LL| | middle_macro!() + LL| | }; + LL| |} + LL| | + LL| |// In each of these three functions, the macro call should be instrumented, + LL| |// and should have an execution count of 1. + LL| |// + LL| |// Each function contains some extra code to ensure that control flow is + LL| |// non-trivial. + LL| | + LL| 1|fn uses_inner_macro() { + LL| 1| if core::hint::black_box(true) { + LL| 1| say("before inner_macro"); + LL| 1| inner_macro!(); + LL| 1| say("after inner_macro"); + LL| 0| } + LL| 1|} + LL| | + LL| 1|fn uses_middle_macro() { + LL| 1| if core::hint::black_box(true) { + LL| 1| say("before middle_macro"); + LL| 1| middle_macro!(); + LL| 1| say("after middle_macro") + LL| 0| } + LL| 1|} + LL| | + LL| 1|fn uses_outer_macro() { + LL| 1| if core::hint::black_box(true) { + LL| 1| say("before outer_macro"); + LL| 1| outer_macro!(); + LL| 1| say("after outer_macro"); + LL| 0| } + LL| 1|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | uses_inner_macro(); + LL| | uses_middle_macro(); + LL| | uses_outer_macro(); + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |fn say(message: &str) { + LL| | println!("{message}"); + LL| |} + diff --git a/tests/coverage/macros/pass-through.rs b/tests/coverage/macros/pass-through.rs new file mode 100644 index 0000000000000..6e5ad3d93b737 --- /dev/null +++ b/tests/coverage/macros/pass-through.rs @@ -0,0 +1,70 @@ +#![feature(coverage_attribute)] +//@ edition: 2024 + +// Test that when a macro expands to another macro, without any significant +// spans of its own, that this doesn't cause coverage instrumentation to give +// up and ignore the inner spans. + +macro_rules! inner_macro { + () => { + if core::hint::black_box(true) { + say("true"); + } else { + say("false"); + } + }; +} + +macro_rules! middle_macro { + () => { + inner_macro!() + }; +} + +macro_rules! outer_macro { + () => { + middle_macro!() + }; +} + +// In each of these three functions, the macro call should be instrumented, +// and should have an execution count of 1. +// +// Each function contains some extra code to ensure that control flow is +// non-trivial. + +fn uses_inner_macro() { + if core::hint::black_box(true) { + say("before inner_macro"); + inner_macro!(); + say("after inner_macro"); + } +} + +fn uses_middle_macro() { + if core::hint::black_box(true) { + say("before middle_macro"); + middle_macro!(); + say("after middle_macro") + } +} + +fn uses_outer_macro() { + if core::hint::black_box(true) { + say("before outer_macro"); + outer_macro!(); + say("after outer_macro"); + } +} + +#[coverage(off)] +fn main() { + uses_inner_macro(); + uses_middle_macro(); + uses_outer_macro(); +} + +#[coverage(off)] +fn say(message: &str) { + println!("{message}"); +} From 7a3e5cd57e67cc8c0715996ec4cccc1b63a2506f Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 2 Dec 2025 13:11:33 +1100 Subject: [PATCH 26/27] coverage: Sort the expansion tree in depth-first order This makes it possible for subsequent operations to iterate over all nodes, while assuming that every node occurs before all of its descendants. --- .../src/coverage/expansion.rs | 38 ++++++++++++++++++- .../src/coverage/mappings.rs | 17 +++++++-- .../rustc_mir_transform/src/coverage/mod.rs | 13 ++++--- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/expansion.rs b/compiler/rustc_mir_transform/src/coverage/expansion.rs index 0288afd95990d..16c37d6e6cf00 100644 --- a/compiler/rustc_mir_transform/src/coverage/expansion.rs +++ b/compiler/rustc_mir_transform/src/coverage/expansion.rs @@ -6,6 +6,7 @@ use rustc_span::{ExpnId, ExpnKind, Span}; use crate::coverage::from_mir; use crate::coverage::graph::CoverageGraph; use crate::coverage::hir_info::ExtractedHirInfo; +use crate::coverage::mappings::MappingsError; #[derive(Clone, Copy, Debug)] pub(crate) struct SpanWithBcb { @@ -62,6 +63,8 @@ pub(crate) struct ExpnNode { /// but is helpful for debugging and might be useful later. #[expect(dead_code)] pub(crate) expn_id: ExpnId, + /// Index of this node in a depth-first traversal from the root. + pub(crate) dfs_rank: usize, // Useful info extracted from `ExpnData`. pub(crate) expn_kind: ExpnKind, @@ -100,6 +103,7 @@ impl ExpnNode { Self { expn_id, + dfs_rank: usize::MAX, expn_kind: expn_data.kind, call_site, @@ -124,7 +128,7 @@ pub(crate) fn build_expn_tree( mir_body: &mir::Body<'_>, hir_info: &ExtractedHirInfo, graph: &CoverageGraph, -) -> ExpnTree { +) -> Result { let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph); let mut nodes = FxIndexMap::default(); @@ -157,6 +161,9 @@ pub(crate) fn build_expn_tree( } } + // Sort the tree nodes into depth-first order. + sort_nodes_depth_first(&mut nodes)?; + // If we have a span for the function signature, associate it with the // corresponding expansion tree node. if let Some(fn_sig_span) = hir_info.fn_sig_span @@ -189,5 +196,32 @@ pub(crate) fn build_expn_tree( } } - ExpnTree { nodes } + Ok(ExpnTree { nodes }) +} + +/// Sorts the tree nodes in the map into depth-first order. +/// +/// This allows subsequent operations to iterate over all nodes, while assuming +/// that every node occurs before all of its descendants. +fn sort_nodes_depth_first(nodes: &mut FxIndexMap) -> Result<(), MappingsError> { + let mut dfs_stack = vec![ExpnId::root()]; + let mut next_dfs_rank = 0usize; + while let Some(expn_id) = dfs_stack.pop() { + if let Some(node) = nodes.get_mut(&expn_id) { + node.dfs_rank = next_dfs_rank; + next_dfs_rank += 1; + dfs_stack.extend(node.child_expn_ids.iter().rev().copied()); + } + } + nodes.sort_by_key(|_expn_id, node| node.dfs_rank); + + // Verify that the depth-first search visited each node exactly once. + for (i, &ExpnNode { dfs_rank, .. }) in nodes.values().enumerate() { + if dfs_rank != i { + tracing::debug!(dfs_rank, i, "expansion tree node's rank does not match its index"); + return Err(MappingsError::TreeSortFailure); + } + } + + Ok(()) } diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index 56f2db90ff8cd..4581afa99a31b 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -11,6 +11,13 @@ use crate::coverage::graph::CoverageGraph; use crate::coverage::hir_info::ExtractedHirInfo; use crate::coverage::spans::extract_refined_covspans; +/// Indicates why mapping extraction failed, for debug-logging purposes. +#[derive(Debug)] +pub(crate) enum MappingsError { + NoMappings, + TreeSortFailure, +} + #[derive(Default)] pub(crate) struct ExtractedMappings { pub(crate) mappings: Vec, @@ -23,8 +30,8 @@ pub(crate) fn extract_mappings_from_mir<'tcx>( mir_body: &mir::Body<'tcx>, hir_info: &ExtractedHirInfo, graph: &CoverageGraph, -) -> ExtractedMappings { - let expn_tree = expansion::build_expn_tree(mir_body, hir_info, graph); +) -> Result { + let expn_tree = expansion::build_expn_tree(mir_body, hir_info, graph)?; let mut mappings = vec![]; @@ -33,7 +40,11 @@ pub(crate) fn extract_mappings_from_mir<'tcx>( extract_branch_mappings(mir_body, hir_info, graph, &expn_tree, &mut mappings); - ExtractedMappings { mappings } + if mappings.is_empty() { + tracing::debug!("no mappings were extracted"); + return Err(MappingsError::NoMappings); + } + Ok(ExtractedMappings { mappings }) } fn resolve_block_markers( diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 24a61c9b4a1bd..be8b02f61133d 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -73,12 +73,13 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: //////////////////////////////////////////////////// // Extract coverage spans and other mapping info from MIR. let ExtractedMappings { mappings } = - mappings::extract_mappings_from_mir(tcx, mir_body, &hir_info, &graph); - if mappings.is_empty() { - // No spans could be converted into valid mappings, so skip this function. - debug!("no spans could be converted into valid mappings; skipping"); - return; - } + match mappings::extract_mappings_from_mir(tcx, mir_body, &hir_info, &graph) { + Ok(m) => m, + Err(error) => { + tracing::debug!(?error, "mapping extraction failed; skipping this function"); + return; + } + }; // Use the coverage graph to prepare intermediate data that will eventually // be used to assign physical counters and counter expressions to points in From 986db13c178126167b4c7e0c4eb76b1dad3908b7 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 3 Dec 2025 17:28:47 +1100 Subject: [PATCH 27/27] coverage: Use the sorted expansion tree to determine min/max BCBs --- .../src/coverage/expansion.rs | 77 +++++++++++-------- .../rustc_mir_transform/src/coverage/spans.rs | 12 +-- compiler/rustc_mir_transform/src/lib.rs | 1 - 3 files changed, 49 insertions(+), 41 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/expansion.rs b/compiler/rustc_mir_transform/src/coverage/expansion.rs index 16c37d6e6cf00..77aec902faad5 100644 --- a/compiler/rustc_mir_transform/src/coverage/expansion.rs +++ b/compiler/rustc_mir_transform/src/coverage/expansion.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry}; use rustc_middle::mir; use rustc_middle::mir::coverage::{BasicCoverageBlock, BranchSpan}; @@ -23,38 +24,6 @@ impl ExpnTree { pub(crate) fn get(&self, expn_id: ExpnId) -> Option<&ExpnNode> { self.nodes.get(&expn_id) } - - /// Yields the tree node for the given expansion ID (if present), followed - /// by the nodes of all of its descendants in depth-first order. - pub(crate) fn iter_node_and_descendants( - &self, - root_expn_id: ExpnId, - ) -> impl Iterator { - gen move { - let Some(root_node) = self.get(root_expn_id) else { return }; - yield root_node; - - // Stack of child-node-ID iterators that drives the depth-first traversal. - let mut iter_stack = vec![root_node.child_expn_ids.iter()]; - - while let Some(curr_iter) = iter_stack.last_mut() { - // Pull the next ID from the top of the stack. - let Some(&curr_id) = curr_iter.next() else { - iter_stack.pop(); - continue; - }; - - // Yield this node. - let Some(node) = self.get(curr_id) else { continue }; - yield node; - - // Push the node's children, to be traversed next. - if !node.child_expn_ids.is_empty() { - iter_stack.push(node.child_expn_ids.iter()); - } - } - } - } } #[derive(Debug)] @@ -85,6 +54,10 @@ pub(crate) struct ExpnNode { pub(crate) spans: Vec, /// Expansions whose call-site is in this expansion. pub(crate) child_expn_ids: FxIndexSet, + /// The "minimum" and "maximum" BCBs (in dominator order) of ordinary spans + /// belonging to this tree node and all of its descendants. Used when + /// creating a single code mapping representing an entire child expansion. + pub(crate) minmax_bcbs: Option, /// Branch spans (recorded during MIR building) belonging to this expansion. pub(crate) branch_spans: Vec, @@ -114,6 +87,7 @@ impl ExpnNode { spans: vec![], child_expn_ids: FxIndexSet::default(), + minmax_bcbs: None, branch_spans: vec![], @@ -164,6 +138,17 @@ pub(crate) fn build_expn_tree( // Sort the tree nodes into depth-first order. sort_nodes_depth_first(&mut nodes)?; + // For each node, determine its "minimum" and "maximum" BCBs, based on its + // own spans and its immediate children. This relies on the nodes having + // been sorted, so that each node's children are processed before the node + // itself. + for i in (0..nodes.len()).rev() { + // Computing a node's min/max BCBs requires a shared ref to other nodes. + let minmax_bcbs = minmax_bcbs_for_expn_tree_node(graph, &nodes, &nodes[i]); + // Now we can mutate the current node to set its min/max BCBs. + nodes[i].minmax_bcbs = minmax_bcbs; + } + // If we have a span for the function signature, associate it with the // corresponding expansion tree node. if let Some(fn_sig_span) = hir_info.fn_sig_span @@ -225,3 +210,31 @@ fn sort_nodes_depth_first(nodes: &mut FxIndexMap) -> Result<() Ok(()) } + +#[derive(Clone, Copy, Debug)] +pub(crate) struct MinMaxBcbs { + pub(crate) min: BasicCoverageBlock, + pub(crate) max: BasicCoverageBlock, +} + +/// For a single node in the expansion tree, compute its "minimum" and "maximum" +/// BCBs (in dominator order), from among the BCBs of its immediate spans, +/// and the min/max of its immediate children. +fn minmax_bcbs_for_expn_tree_node( + graph: &CoverageGraph, + nodes: &FxIndexMap, + node: &ExpnNode, +) -> Option { + let immediate_span_bcbs = node.spans.iter().map(|sp: &SpanWithBcb| sp.bcb); + let child_minmax_bcbs = node + .child_expn_ids + .iter() + .flat_map(|id| nodes.get(id)) + .flat_map(|child| child.minmax_bcbs) + .flat_map(|MinMaxBcbs { min, max }| [min, max]); + + let (min, max) = Iterator::chain(immediate_span_bcbs, child_minmax_bcbs) + .minmax_by(|&a, &b| graph.cmp_in_dominator_order(a, b)) + .into_option()?; + Some(MinMaxBcbs { min, max }) +} diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index bc26a8ccc47ac..b1ce0069b43a8 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -39,8 +39,7 @@ pub(super) fn extract_refined_covspans<'tcx>( // For each expansion with its call-site in the body span, try to // distill a corresponding covspan. for &child_expn_id in &node.child_expn_ids { - if let Some(covspan) = single_covspan_for_child_expn(tcx, graph, &expn_tree, child_expn_id) - { + if let Some(covspan) = single_covspan_for_child_expn(tcx, &expn_tree, child_expn_id) { covspans.push(covspan); } } @@ -127,24 +126,21 @@ pub(super) fn extract_refined_covspans<'tcx>( /// For a single child expansion, try to distill it into a single span+BCB mapping. fn single_covspan_for_child_expn( tcx: TyCtxt<'_>, - graph: &CoverageGraph, expn_tree: &ExpnTree, expn_id: ExpnId, ) -> Option { let node = expn_tree.get(expn_id)?; - - let bcbs = - expn_tree.iter_node_and_descendants(expn_id).flat_map(|n| n.spans.iter().map(|s| s.bcb)); + let minmax_bcbs = node.minmax_bcbs?; let bcb = match node.expn_kind { // For bang-macros (e.g. `assert!`, `trace!`) and for `await`, taking // the "first" BCB in dominator order seems to give good results. ExpnKind::Macro(MacroKind::Bang, _) | ExpnKind::Desugaring(DesugaringKind::Await) => { - bcbs.min_by(|&a, &b| graph.cmp_in_dominator_order(a, b))? + minmax_bcbs.min } // For other kinds of expansion, taking the "last" (most-dominated) BCB // seems to give good results. - _ => bcbs.max_by(|&a, &b| graph.cmp_in_dominator_order(a, b))?, + _ => minmax_bcbs.max, }; // For bang-macro expansions, limit the call-site span to just the macro diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index f12561492de47..afdfa35f97c06 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -5,7 +5,6 @@ #![feature(const_type_name)] #![feature(cow_is_borrowed)] #![feature(file_buffered)] -#![feature(gen_blocks)] #![feature(if_let_guard)] #![feature(impl_trait_in_assoc_type)] #![feature(try_blocks)]