Skip to content
Merged
96 changes: 63 additions & 33 deletions src/librustdoc/formats/renderer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use rustc_data_structures::profiling::SelfProfilerRef;
use rustc_middle::ty::TyCtxt;
use tracing::debug;

use crate::clean;
use crate::config::RenderOptions;
Expand All @@ -17,6 +17,18 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
///
/// This is true for html, and false for json. See #80664
const RUN_ON_MODULE: bool;
/// This associated type is the type where the current module information is stored.
///
/// For each module, we go through their items by calling for each item:
///
/// 1. save_module_data
/// 2. item
/// 3. set_back_info
///
/// However,the `item` method might update information in `self` (for example if the child is
/// a module). To prevent it to impact the other children of the current module, we need to
/// reset the information between each call to `item` by using `set_back_info`.
type ModuleData;

/// Sets up any state required for the renderer. When this is called the cache has already been
/// populated.
Expand All @@ -27,8 +39,20 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
tcx: TyCtxt<'tcx>,
) -> Result<(Self, clean::Crate), Error>;

/// Make a new renderer to render a child of the item currently being rendered.
fn make_child_renderer(&self) -> Self;
/// This method is called right before call [`Self::item`]. This method returns a type
/// containing information that needs to be reset after the [`Self::item`] method has been
/// called with the [`Self::set_back_info`] method.
///
/// In short it goes like this:
///
/// ```ignore (not valid code)
/// let reset_data = type.save_module_data();
/// type.item(item)?;
/// type.set_back_info(reset_data);
/// ```
fn save_module_data(&mut self) -> Self::ModuleData;
/// Used to reset current module's information.
fn set_back_info(&mut self, info: Self::ModuleData);

/// Renders a single non-module item. This means no recursive sub-item rendering is required.
fn item(&mut self, item: clean::Item) -> Result<(), Error>;
Expand All @@ -47,6 +71,40 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
fn cache(&self) -> &Cache;
}

fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
cx: &mut T,
item: clean::Item,
prof: &SelfProfilerRef,
) -> Result<(), Error> {
if item.is_mod() && T::RUN_ON_MODULE {
// modules are special because they add a namespace. We also need to
// recurse into the items of the module as well.
let _timer =
prof.generic_activity_with_arg("render_mod_item", item.name.unwrap().to_string());

cx.mod_item_in(&item)?;
let (clean::StrippedItem(box clean::ModuleItem(module)) | clean::ModuleItem(module)) =
item.inner.kind
else {
unreachable!()
};
for it in module.items {
let info = cx.save_module_data();
run_format_inner(cx, it, prof)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old version used a heap-allocated stack, and this version instead uses the call stack.

I think that's fine, as long as you call stacker?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering we recurse on modules (other items, they stop at one level), I think we're on the safe side. Do you see a case where it could be an issue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it's going to be a problem. I just noticed that the old didn't do this.

If you don't think it's a problem, then we can go ahead with it. There might be other spots where rustdoc directly recurses on modules anyway.

cx.set_back_info(info);
}

cx.mod_item_out()?;
// FIXME: checking `item.name.is_some()` is very implicit and leads to lots of special
// cases. Use an explicit match instead.
} else if let Some(item_name) = item.name
&& !item.is_extern_crate()
{
prof.generic_activity_with_arg("render_item", item_name.as_str()).run(|| cx.item(item))?;
}
Ok(())
}

/// Main method for rendering a crate.
pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>(
krate: clean::Crate,
Expand All @@ -66,36 +124,8 @@ pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>(
}

// Render the crate documentation
let mut work = vec![(format_renderer.make_child_renderer(), krate.module)];

while let Some((mut cx, item)) = work.pop() {
if item.is_mod() && T::RUN_ON_MODULE {
// modules are special because they add a namespace. We also need to
// recurse into the items of the module as well.
let _timer =
prof.generic_activity_with_arg("render_mod_item", item.name.unwrap().to_string());

cx.mod_item_in(&item)?;
let (clean::StrippedItem(box clean::ModuleItem(module)) | clean::ModuleItem(module)) =
item.inner.kind
else {
unreachable!()
};
for it in module.items {
debug!("Adding {:?} to worklist", it.name);
work.push((cx.make_child_renderer(), it));
}

cx.mod_item_out()?;
// FIXME: checking `item.name.is_some()` is very implicit and leads to lots of special
// cases. Use an explicit match instead.
} else if let Some(item_name) = item.name
&& !item.is_extern_crate()
{
prof.generic_activity_with_arg("render_item", item_name.as_str())
.run(|| cx.item(item))?;
}
}
run_format_inner(&mut format_renderer, krate.module, prof)?;

prof.verbose_generic_activity_with_arg("renderer_after_krate", T::descr())
.run(|| format_renderer.after_krate())
}
117 changes: 66 additions & 51 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use std::sync::OnceLock;
use pulldown_cmark::{
BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html,
};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{Diag, DiagMessage};
use rustc_hir::def_id::LocalDefId;
use rustc_middle::ty::TyCtxt;
Expand Down Expand Up @@ -1886,71 +1886,81 @@ pub struct IdMap {
existing_footnotes: usize,
}

// The map is pre-initialized and cloned each time to avoid reinitializing it repeatedly.
static DEFAULT_ID_MAP: OnceLock<FxHashMap<Cow<'static, str>, usize>> = OnceLock::new();
// The map is pre-initialized and then can be used as is to prevent cloning it for each item
// (in the sidebar rendering).
static DEFAULT_ID_MAP: OnceLock<FxHashSet<&'static str>> = OnceLock::new();

fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
let mut map = FxHashMap::default();
fn init_id_map() -> FxHashSet<&'static str> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does performance compare if this is a function that returns bool?

fn is_builtin_id(id: &str) -> bool {
    match id {
        // This is the list of IDs used in JavaScript.
		| "help"
		| "settings"
		| "not-displayed"
		| "alternative-display"
		| "search"
		| "crate-search"
		| "crate-search-div"
		// This is the list of IDs used in HTML generated in Rust (including the ones
		// used in tera template files).
		| "themeStyle"
		| "settings-menu"
		| "help-button"
		| "sidebar-button"
		| "main-content"
		| "toggle-all-docs"
		| "all-types"
		| "default-settings"
		| "sidebar-vars"
		| "copy-path"
		| "rustdoc-toc"
		| "rustdoc-modnav"
		// This is the list of IDs used by rustdoc sections (but still generated by
		// rustdoc).
		| "fields"
		| "variants"
		| "implementors-list"
		| "synthetic-implementors-list"
		| "foreign-impls"
		| "implementations"
		| "trait-implementations"
		| "synthetic-implementations"
		| "blanket-implementations"
		| "required-associated-types"
		| "provided-associated-types"
		| "provided-associated-consts"
		| "required-associated-consts"
		| "required-methods"
		| "provided-methods"
		| "dyn-compatibility"
		| "implementors"
		| "synthetic-implementors"
		| "implementations-list"
		| "trait-implementations-list"
		| "synthetic-implementations-list"
		| "blanket-implementations-list"
		| "deref-methods"
		| "layout"
		| "aliased-type"
		=> true,
		_ => false,
    }
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

matches!. Let's find out in the next set of ideas I have as follow-up?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine. It can be tried as a follow-up PR.

let mut map = FxHashSet::default();
// This is the list of IDs used in JavaScript.
map.insert("help".into(), 1);
map.insert("settings".into(), 1);
map.insert("not-displayed".into(), 1);
map.insert("alternative-display".into(), 1);
map.insert("search".into(), 1);
map.insert("crate-search".into(), 1);
map.insert("crate-search-div".into(), 1);
map.insert("help");
map.insert("settings");
map.insert("not-displayed");
map.insert("alternative-display");
map.insert("search");
map.insert("crate-search");
map.insert("crate-search-div");
// This is the list of IDs used in HTML generated in Rust (including the ones
// used in tera template files).
map.insert("themeStyle".into(), 1);
map.insert("settings-menu".into(), 1);
map.insert("help-button".into(), 1);
map.insert("sidebar-button".into(), 1);
map.insert("main-content".into(), 1);
map.insert("toggle-all-docs".into(), 1);
map.insert("all-types".into(), 1);
map.insert("default-settings".into(), 1);
map.insert("sidebar-vars".into(), 1);
map.insert("copy-path".into(), 1);
map.insert("rustdoc-toc".into(), 1);
map.insert("rustdoc-modnav".into(), 1);
map.insert("themeStyle");
map.insert("settings-menu");
map.insert("help-button");
map.insert("sidebar-button");
map.insert("main-content");
map.insert("toggle-all-docs");
map.insert("all-types");
map.insert("default-settings");
map.insert("sidebar-vars");
map.insert("copy-path");
map.insert("rustdoc-toc");
map.insert("rustdoc-modnav");
// This is the list of IDs used by rustdoc sections (but still generated by
// rustdoc).
map.insert("fields".into(), 1);
map.insert("variants".into(), 1);
map.insert("implementors-list".into(), 1);
map.insert("synthetic-implementors-list".into(), 1);
map.insert("foreign-impls".into(), 1);
map.insert("implementations".into(), 1);
map.insert("trait-implementations".into(), 1);
map.insert("synthetic-implementations".into(), 1);
map.insert("blanket-implementations".into(), 1);
map.insert("required-associated-types".into(), 1);
map.insert("provided-associated-types".into(), 1);
map.insert("provided-associated-consts".into(), 1);
map.insert("required-associated-consts".into(), 1);
map.insert("required-methods".into(), 1);
map.insert("provided-methods".into(), 1);
map.insert("dyn-compatibility".into(), 1);
map.insert("implementors".into(), 1);
map.insert("synthetic-implementors".into(), 1);
map.insert("implementations-list".into(), 1);
map.insert("trait-implementations-list".into(), 1);
map.insert("synthetic-implementations-list".into(), 1);
map.insert("blanket-implementations-list".into(), 1);
map.insert("deref-methods".into(), 1);
map.insert("layout".into(), 1);
map.insert("aliased-type".into(), 1);
map.insert("fields");
map.insert("variants");
map.insert("implementors-list");
map.insert("synthetic-implementors-list");
map.insert("foreign-impls");
map.insert("implementations");
map.insert("trait-implementations");
map.insert("synthetic-implementations");
map.insert("blanket-implementations");
map.insert("required-associated-types");
map.insert("provided-associated-types");
map.insert("provided-associated-consts");
map.insert("required-associated-consts");
map.insert("required-methods");
map.insert("provided-methods");
map.insert("dyn-compatibility");
map.insert("implementors");
map.insert("synthetic-implementors");
map.insert("implementations-list");
map.insert("trait-implementations-list");
map.insert("synthetic-implementations-list");
map.insert("blanket-implementations-list");
map.insert("deref-methods");
map.insert("layout");
map.insert("aliased-type");
map
}

impl IdMap {
pub fn new() -> Self {
IdMap { map: DEFAULT_ID_MAP.get_or_init(init_id_map).clone(), existing_footnotes: 0 }
IdMap { map: FxHashMap::default(), existing_footnotes: 0 }
}

pub(crate) fn derive<S: AsRef<str> + ToString>(&mut self, candidate: S) -> String {
let id = match self.map.get_mut(candidate.as_ref()) {
None => candidate.to_string(),
None => {
let candidate = candidate.to_string();
if DEFAULT_ID_MAP.get_or_init(init_id_map).contains(candidate.as_str()) {
let id = format!("{}-{}", candidate, 1);
self.map.insert(candidate.into(), 2);
id
} else {
candidate
}
}
Some(a) => {
let id = format!("{}-{}", candidate.as_ref(), *a);
*a += 1;
Expand All @@ -1970,4 +1980,9 @@ impl IdMap {
closure(self, &mut existing_footnotes);
self.existing_footnotes = existing_footnotes;
}

pub(crate) fn clear(&mut self) {
self.map.clear();
self.existing_footnotes = 0;
}
}
Loading
Loading