Skip to content

Commit 69fd5e4

Browse files
committed
Auto merge of rust-lang#136828 - yotamofek:pr/rustdoc/more-laziness, r=GuillaumeGomez,aDotInTheVoid
Do more lazy-formatting in `librustdoc` 🦥 Modify some formatting to be lazy, i.e. to not allocate interim strings that are later formatted into different strings. Commits are small and stand on their own, and should mostly compile separately. (The first one doesn't compile due to `dead_code` because all it does is introduce a helper used in later commits) Really excited about this one, local perf results are really good. I'd love a perf run to see how this looks on CI. This is the comparison of `instructions:u` count between master and this PR, on my computer: # Summary | | Range | Mean | Count | |:---:|:---:|:---:|:---:| | Regressions | - | 0.00% | 0 | | Improvements | -8.03%, -0.40% | -2.93% | 5 | | All | -8.03%, -0.40% | -2.93% | 5 | # Primary benchmarks | Benchmark | Profile | Scenario | % Change | Significance Factor | |:---:|:---:|:---:|:---:|:---:| | typenum-1.17.0 | doc | full | -8.03% | 40.16x | | nalgebra-0.33.0 | doc | full | -4.19% | 20.97x | | stm32f4-0.14.0 | doc | full | -1.35% | 6.73x | | libc-0.2.124 | doc | full | -0.67% | 3.33x | | cranelift-codegen-0.82.1 | doc | full | -0.40% | 1.99x |
2 parents 8c07d14 + 5cc64e8 commit 69fd5e4

File tree

7 files changed

+157
-95
lines changed

7 files changed

+157
-95
lines changed

src/librustdoc/clean/cfg.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use rustc_session::parse::ParseSess;
1313
use rustc_span::Span;
1414
use rustc_span::symbol::{Symbol, sym};
1515

16+
use crate::display::Joined as _;
1617
use crate::html::escape::Escape;
17-
use crate::joined::Joined as _;
1818

1919
#[cfg(test)]
2020
mod tests;

src/librustdoc/joined.rs renamed to src/librustdoc/display.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! Various utilities for working with [`fmt::Display`] implementations.
2+
13
use std::fmt::{self, Display, Formatter};
24

35
pub(crate) trait Joined: IntoIterator {
@@ -27,3 +29,19 @@ where
2729
Ok(())
2830
}
2931
}
32+
33+
pub(crate) trait MaybeDisplay {
34+
/// For a given `Option<T: Display>`, returns a `Display` implementation that will display `t` if `Some(t)`, or nothing if `None`.
35+
fn maybe_display(self) -> impl Display;
36+
}
37+
38+
impl<T: Display> MaybeDisplay for Option<T> {
39+
fn maybe_display(self) -> impl Display {
40+
fmt::from_fn(move |f| {
41+
if let Some(t) = self.as_ref() {
42+
t.fmt(f)?;
43+
}
44+
Ok(())
45+
})
46+
}
47+
}

src/librustdoc/html/format.rs

+27-20
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ use super::url_parts_builder::{UrlPartsBuilder, estimate_item_path_byte_length};
3030
use crate::clean::types::ExternalLocation;
3131
use crate::clean::utils::find_nearest_parent_module;
3232
use crate::clean::{self, ExternalCrate, PrimitiveType};
33+
use crate::display::Joined as _;
3334
use crate::formats::cache::Cache;
3435
use crate::formats::item_type::ItemType;
3536
use crate::html::escape::{Escape, EscapeBodyText};
3637
use crate::html::render::Context;
37-
use crate::joined::Joined as _;
3838
use crate::passes::collect_intra_doc_links::UrlFragment;
3939

4040
pub(crate) fn write_str(s: &mut String, f: fmt::Arguments<'_>) {
@@ -709,19 +709,22 @@ fn resolved_path(
709709
if w.alternate() {
710710
write!(w, "{}{:#}", last.name, last.args.print(cx))?;
711711
} else {
712-
let path = if use_absolute {
713-
if let Ok((_, _, fqp)) = href(did, cx) {
714-
format!(
715-
"{path}::{anchor}",
716-
path = join_with_double_colon(&fqp[..fqp.len() - 1]),
717-
anchor = anchor(did, *fqp.last().unwrap(), cx)
718-
)
712+
let path = fmt::from_fn(|f| {
713+
if use_absolute {
714+
if let Ok((_, _, fqp)) = href(did, cx) {
715+
write!(
716+
f,
717+
"{path}::{anchor}",
718+
path = join_with_double_colon(&fqp[..fqp.len() - 1]),
719+
anchor = anchor(did, *fqp.last().unwrap(), cx)
720+
)
721+
} else {
722+
write!(f, "{}", last.name)
723+
}
719724
} else {
720-
last.name.to_string()
725+
write!(f, "{}", anchor(did, last.name, cx))
721726
}
722-
} else {
723-
anchor(did, last.name, cx).to_string()
724-
};
727+
});
725728
write!(w, "{path}{args}", args = last.args.print(cx))?;
726729
}
727730
Ok(())
@@ -749,16 +752,20 @@ fn primitive_link_fragment(
749752
match m.primitive_locations.get(&prim) {
750753
Some(&def_id) if def_id.is_local() => {
751754
let len = cx.current.len();
752-
let path = if len == 0 {
753-
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
754-
format!("{cname_sym}/")
755-
} else {
756-
"../".repeat(len - 1)
757-
};
755+
let path = fmt::from_fn(|f| {
756+
if len == 0 {
757+
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
758+
write!(f, "{cname_sym}/")?;
759+
} else {
760+
for _ in 0..(len - 1) {
761+
f.write_str("../")?;
762+
}
763+
}
764+
Ok(())
765+
});
758766
write!(
759767
f,
760-
"<a class=\"primitive\" href=\"{}primitive.{}.html{fragment}\">",
761-
path,
768+
"<a class=\"primitive\" href=\"{path}primitive.{}.html{fragment}\">",
762769
prim.as_sym()
763770
)?;
764771
needs_termination = true;

src/librustdoc/html/render/context.rs

+17-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::cell::RefCell;
22
use std::collections::BTreeMap;
3+
use std::fmt::{self, Write as _};
4+
use std::io;
35
use std::path::{Path, PathBuf};
46
use std::sync::mpsc::{Receiver, channel};
5-
use std::{fmt, io};
67

78
use rinja::Template;
89
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
@@ -265,21 +266,25 @@ impl<'tcx> Context<'tcx> {
265266
// preventing an infinite redirection loop in the generated
266267
// documentation.
267268

268-
let mut path = String::new();
269-
for name in &names[..names.len() - 1] {
270-
path.push_str(name.as_str());
271-
path.push('/');
272-
}
273-
path.push_str(&item_path(ty, names.last().unwrap().as_str()));
269+
let path = fmt::from_fn(|f| {
270+
for name in &names[..names.len() - 1] {
271+
write!(f, "{name}/")?;
272+
}
273+
write!(f, "{}", item_path(ty, names.last().unwrap().as_str()))
274+
});
274275
match self.shared.redirections {
275276
Some(ref redirections) => {
276277
let mut current_path = String::new();
277278
for name in &self.current {
278279
current_path.push_str(name.as_str());
279280
current_path.push('/');
280281
}
281-
current_path.push_str(&item_path(ty, names.last().unwrap().as_str()));
282-
redirections.borrow_mut().insert(current_path, path);
282+
let _ = write!(
283+
current_path,
284+
"{}",
285+
item_path(ty, names.last().unwrap().as_str())
286+
);
287+
redirections.borrow_mut().insert(current_path, path.to_string());
283288
}
284289
None => {
285290
return layout::redirect(&format!(
@@ -854,9 +859,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
854859
if !buf.is_empty() {
855860
let name = item.name.as_ref().unwrap();
856861
let item_type = item.type_();
857-
let file_name = &item_path(item_type, name.as_str());
862+
let file_name = item_path(item_type, name.as_str()).to_string();
858863
self.shared.ensure_dir(&self.dst)?;
859-
let joint_dst = self.dst.join(file_name);
864+
let joint_dst = self.dst.join(&file_name);
860865
self.shared.fs.write(joint_dst, buf)?;
861866

862867
if !self.info.render_redirect_pages {
@@ -873,7 +878,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
873878
format!("{crate_name}/{file_name}"),
874879
);
875880
} else {
876-
let v = layout::redirect(file_name);
881+
let v = layout::redirect(&file_name);
877882
let redir_dst = self.dst.join(redir_name);
878883
self.shared.fs.write(redir_dst, v)?;
879884
}

src/librustdoc/html/render/mod.rs

+65-31
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub(crate) use self::context::*;
6363
pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources};
6464
pub(crate) use self::write_shared::*;
6565
use crate::clean::{self, ItemId, RenderedLink};
66+
use crate::display::{Joined as _, MaybeDisplay as _};
6667
use crate::error::Error;
6768
use crate::formats::Impl;
6869
use crate::formats::cache::Cache;
@@ -568,17 +569,27 @@ fn document_short<'a, 'cx: 'a>(
568569
let (mut summary_html, has_more_content) =
569570
MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
570571

571-
if has_more_content {
572-
let link = format!(" <a{}>Read more</a>", assoc_href_attr(item, link, cx));
572+
let link = if has_more_content {
573+
let link = fmt::from_fn(|f| {
574+
write!(
575+
f,
576+
" <a{}>Read more</a>",
577+
assoc_href_attr(item, link, cx).maybe_display()
578+
)
579+
});
573580

574581
if let Some(idx) = summary_html.rfind("</p>") {
575-
summary_html.insert_str(idx, &link);
582+
summary_html.insert_str(idx, &link.to_string());
583+
None
576584
} else {
577-
summary_html.push_str(&link);
585+
Some(link)
578586
}
587+
} else {
588+
None
579589
}
590+
.maybe_display();
580591

581-
write!(f, "<div class='docblock'>{summary_html}</div>")?;
592+
write!(f, "<div class='docblock'>{summary_html}{link}</div>")?;
582593
}
583594
Ok(())
584595
})
@@ -788,13 +799,23 @@ pub(crate) fn render_impls(
788799
}
789800

790801
/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
791-
fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
802+
fn assoc_href_attr<'a, 'tcx>(
803+
it: &clean::Item,
804+
link: AssocItemLink<'a>,
805+
cx: &Context<'tcx>,
806+
) -> Option<impl fmt::Display + 'a + Captures<'tcx>> {
792807
let name = it.name.unwrap();
793808
let item_type = it.type_();
794809

810+
enum Href<'a> {
811+
AnchorId(&'a str),
812+
Anchor(ItemType),
813+
Url(String, ItemType),
814+
}
815+
795816
let href = match link {
796-
AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{id}")),
797-
AssocItemLink::Anchor(None) => Some(format!("#{item_type}.{name}")),
817+
AssocItemLink::Anchor(Some(ref id)) => Href::AnchorId(id),
818+
AssocItemLink::Anchor(None) => Href::Anchor(item_type),
798819
AssocItemLink::GotoSource(did, provided_methods) => {
799820
// We're creating a link from the implementation of an associated item to its
800821
// declaration in the trait declaration.
@@ -814,7 +835,7 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>)
814835
};
815836

816837
match href(did.expect_def_id(), cx) {
817-
Ok((url, ..)) => Some(format!("{url}#{item_type}.{name}")),
838+
Ok((url, ..)) => Href::Url(url, item_type),
818839
// The link is broken since it points to an external crate that wasn't documented.
819840
// Do not create any link in such case. This is better than falling back to a
820841
// dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item
@@ -826,15 +847,25 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>)
826847
// those two items are distinct!
827848
// In this scenario, the actual `id` of this impl item would be
828849
// `#{item_type}.{name}-{n}` for some number `n` (a disambiguator).
829-
Err(HrefError::DocumentationNotBuilt) => None,
830-
Err(_) => Some(format!("#{item_type}.{name}")),
850+
Err(HrefError::DocumentationNotBuilt) => return None,
851+
Err(_) => Href::Anchor(item_type),
831852
}
832853
}
833854
};
834855

856+
let href = fmt::from_fn(move |f| match &href {
857+
Href::AnchorId(id) => write!(f, "#{id}"),
858+
Href::Url(url, item_type) => {
859+
write!(f, "{url}#{item_type}.{name}")
860+
}
861+
Href::Anchor(item_type) => {
862+
write!(f, "#{item_type}.{name}")
863+
}
864+
});
865+
835866
// If there is no `href` for the reason explained above, simply do not render it which is valid:
836867
// https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
837-
href.map(|href| format!(" href=\"{href}\"")).unwrap_or_default()
868+
Some(fmt::from_fn(move |f| write!(f, " href=\"{href}\"")))
838869
}
839870

840871
#[derive(Debug)]
@@ -865,7 +896,7 @@ fn assoc_const(
865896
"{indent}{vis}const <a{href} class=\"constant\">{name}</a>{generics}: {ty}",
866897
indent = " ".repeat(indent),
867898
vis = visibility_print_with_space(it, cx),
868-
href = assoc_href_attr(it, link, cx),
899+
href = assoc_href_attr(it, link, cx).maybe_display(),
869900
name = it.name.as_ref().unwrap(),
870901
generics = generics.print(cx),
871902
ty = ty.print(cx),
@@ -905,7 +936,7 @@ fn assoc_type(
905936
"{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
906937
indent = " ".repeat(indent),
907938
vis = visibility_print_with_space(it, cx),
908-
href = assoc_href_attr(it, link, cx),
939+
href = assoc_href_attr(it, link, cx).maybe_display(),
909940
name = it.name.as_ref().unwrap(),
910941
generics = generics.print(cx),
911942
),
@@ -948,7 +979,7 @@ fn assoc_method(
948979
let asyncness = header.asyncness.print_with_space();
949980
let safety = header.safety.print_with_space();
950981
let abi = print_abi_with_space(header.abi).to_string();
951-
let href = assoc_href_attr(meth, link, cx);
982+
let href = assoc_href_attr(meth, link, cx).maybe_display();
952983

953984
// NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
954985
let generics_len = format!("{:#}", g.print(cx)).len();
@@ -962,7 +993,7 @@ fn assoc_method(
962993
+ name.as_str().len()
963994
+ generics_len;
964995

965-
let notable_traits = notable_traits_button(&d.output, cx);
996+
let notable_traits = notable_traits_button(&d.output, cx).maybe_display();
966997

967998
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
968999
header_len += 4;
@@ -990,7 +1021,6 @@ fn assoc_method(
9901021
name = name,
9911022
generics = g.print(cx),
9921023
decl = d.full_print(header_len, indent, cx),
993-
notable_traits = notable_traits.unwrap_or_default(),
9941024
where_clause = print_where_clause(g, cx, indent, end_newline),
9951025
),
9961026
);
@@ -1438,7 +1468,10 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
14381468
}
14391469
}
14401470

1441-
pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option<String> {
1471+
pub(crate) fn notable_traits_button<'a, 'tcx>(
1472+
ty: &'a clean::Type,
1473+
cx: &'a Context<'tcx>,
1474+
) -> Option<impl fmt::Display + 'a + Captures<'tcx>> {
14421475
let mut has_notable_trait = false;
14431476

14441477
if ty.is_unit() {
@@ -1480,15 +1513,16 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Optio
14801513
}
14811514
}
14821515

1483-
if has_notable_trait {
1516+
has_notable_trait.then(|| {
14841517
cx.types_with_notable_traits.borrow_mut().insert(ty.clone());
1485-
Some(format!(
1486-
" <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
1487-
ty = Escape(&format!("{:#}", ty.print(cx))),
1488-
))
1489-
} else {
1490-
None
1491-
}
1518+
fmt::from_fn(|f| {
1519+
write!(
1520+
f,
1521+
" <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
1522+
ty = Escape(&format!("{:#}", ty.print(cx))),
1523+
)
1524+
})
1525+
})
14921526
}
14931527

14941528
fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
@@ -2117,11 +2151,11 @@ pub(crate) fn render_impl_summary(
21172151
) {
21182152
let inner_impl = i.inner_impl();
21192153
let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id));
2120-
let aliases = if aliases.is_empty() {
2121-
String::new()
2122-
} else {
2123-
format!(" data-aliases=\"{}\"", aliases.join(","))
2124-
};
2154+
let aliases = (!aliases.is_empty())
2155+
.then_some(fmt::from_fn(|f| {
2156+
write!(f, " data-aliases=\"{}\"", fmt::from_fn(|f| aliases.iter().joined(",", f)))
2157+
}))
2158+
.maybe_display();
21252159
write_str(w, format_args!("<section id=\"{id}\" class=\"impl\"{aliases}>"));
21262160
render_rightside(w, cx, &i.impl_item, RenderMode::Normal);
21272161
write_str(

0 commit comments

Comments
 (0)