Skip to content

Commit

Permalink
Auto merge of rust-lang#133345 - GuillaumeGomez:stop-cloning-context,…
Browse files Browse the repository at this point in the history
… r=<try>

Stop cloning `Context` so much

This is a first step for rust-lang#82381.

It's already big enough so I'll continue in a follow-up once this PR is merged. Next step will be to get rid of `SharedContext` by inlining it directly into `Context`.

cc `@camelid`
r? `@notriddle`
  • Loading branch information
bors committed Nov 23, 2024
2 parents 6e1c115 + 74d6adf commit 0d6f0cd
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 81 deletions.
72 changes: 40 additions & 32 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,7 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
///
/// This is true for html, and false for json. See #80664
const RUN_ON_MODULE: bool;
type InfoType;

/// Sets up any state required for the renderer. When this is called the cache has already been
/// populated.
Expand All @@ -28,7 +29,8 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
) -> 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;
fn make_child_renderer(&mut self) -> Self::InfoType;
fn set_back_info(&mut self, _info: Self::InfoType);

/// 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 +49,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.make_child_renderer();
run_format_inner(cx, it, prof)?;
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 +102,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())
}
12 changes: 7 additions & 5 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ use std::iter::Peekable;
use std::ops::{ControlFlow, Range};
use std::path::PathBuf;
use std::str::{self, CharIndices};
use std::sync::OnceLock;

use pulldown_cmark::{
BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html,
Expand Down Expand Up @@ -1886,12 +1885,10 @@ pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<Rust
#[derive(Clone, Default, Debug)]
pub struct IdMap {
map: FxHashMap<Cow<'static, str>, usize>,
defined_ids: FxHashMap<Cow<'static, str>, usize>,
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();

fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
let mut map = FxHashMap::default();
// This is the list of IDs used in JavaScript.
Expand Down Expand Up @@ -1948,7 +1945,7 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {

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(), defined_ids: init_id_map(), existing_footnotes: 0 }
}

pub(crate) fn derive<S: AsRef<str> + ToString>(&mut self, candidate: S) -> String {
Expand All @@ -1973,4 +1970,9 @@ impl IdMap {
closure(self, &mut existing_footnotes);
self.existing_footnotes = existing_footnotes;
}

pub(crate) fn clear(&mut self) {
self.map = self.defined_ids.clone();
self.existing_footnotes = 0;
}
}
79 changes: 42 additions & 37 deletions src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@ pub(crate) struct Context<'tcx> {
/// The current destination folder of where HTML artifacts should be placed.
/// This changes as the context descends into the module hierarchy.
pub(crate) dst: PathBuf,
/// A flag, which when `true`, will render pages which redirect to the
/// real location of an item. This is used to allow external links to
/// publicly reused items to redirect to the right location.
pub(super) render_redirect_pages: bool,
/// Tracks section IDs for `Deref` targets so they match in both the main
/// body and the sidebar.
pub(super) deref_id_map: DefIdMap<String>,
Expand All @@ -64,21 +60,32 @@ pub(crate) struct Context<'tcx> {
///
/// [#82381]: https://github.com/rust-lang/rust/issues/82381
pub(crate) shared: Rc<SharedContext<'tcx>>,
/// Collection of all types with notable traits referenced in the current module.
pub(crate) types_with_notable_traits: FxIndexSet<clean::Type>,
/// Contains information that needs to be saved and reset after rendering an item which is
/// not a module.
pub(crate) info: ContextInfo,
}

#[derive(Clone, Copy)]
pub(crate) struct ContextInfo {
/// A flag, which when `true`, will render pages which redirect to the
/// real location of an item. This is used to allow external links to
/// publicly reused items to redirect to the right location.
pub(super) render_redirect_pages: bool,
/// This flag indicates whether source links should be generated or not. If
/// the source files are present in the html rendering, then this will be
/// `true`.
pub(crate) include_sources: bool,
/// Collection of all types with notable traits referenced in the current module.
pub(crate) types_with_notable_traits: FxIndexSet<clean::Type>,
/// Field used during rendering, to know if we're inside an inlined item.
pub(crate) is_inside_inlined_module: bool,
}

// `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
#[cfg(all(not(windows), target_pointer_width = "64"))]
rustc_data_structures::static_assert_size!(Context<'_>, 192);
#[cfg(all(windows, target_pointer_width = "64"))]
rustc_data_structures::static_assert_size!(Context<'_>, 200);
impl ContextInfo {
fn new(include_sources: bool) -> Self {
Self { render_redirect_pages: false, include_sources, is_inside_inlined_module: false }
}
}

/// Shared mutable state used in [`Context`] and elsewhere.
pub(crate) struct SharedContext<'tcx> {
Expand Down Expand Up @@ -174,14 +181,16 @@ impl<'tcx> Context<'tcx> {
}

fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String {
let mut render_redirect_pages = self.render_redirect_pages;
let mut render_redirect_pages = self.info.render_redirect_pages;
// If the item is stripped but inlined, links won't point to the item so no need to generate
// a file for it.
if it.is_stripped()
&& let Some(def_id) = it.def_id()
&& def_id.is_local()
{
if self.is_inside_inlined_module || self.shared.cache.inlined_items.contains(&def_id) {
if self.info.is_inside_inlined_module
|| self.shared.cache.inlined_items.contains(&def_id)
{
// For now we're forced to generate a redirect page for stripped items until
// `record_extern_fqn` correctly points to external items.
render_redirect_pages = true;
Expand Down Expand Up @@ -441,6 +450,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
}

const RUN_ON_MODULE: bool = true;
type InfoType = ContextInfo;

fn init(
krate: clean::Crate,
Expand Down Expand Up @@ -562,13 +572,11 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
let mut cx = Context {
current: Vec::new(),
dst,
render_redirect_pages: false,
id_map,
deref_id_map: Default::default(),
shared: Rc::new(scx),
include_sources,
types_with_notable_traits: FxIndexSet::default(),
is_inside_inlined_module: false,
info: ContextInfo::new(include_sources),
};

if emit_crate {
Expand All @@ -582,18 +590,15 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
Ok((cx, krate))
}

fn make_child_renderer(&self) -> Self {
Self {
current: self.current.clone(),
dst: self.dst.clone(),
render_redirect_pages: self.render_redirect_pages,
deref_id_map: Default::default(),
id_map: IdMap::new(),
shared: Rc::clone(&self.shared),
include_sources: self.include_sources,
types_with_notable_traits: FxIndexSet::default(),
is_inside_inlined_module: self.is_inside_inlined_module,
}
fn make_child_renderer(&mut self) -> Self::InfoType {
self.deref_id_map.clear();
self.id_map.clear();
self.types_with_notable_traits.clear();
self.info
}

fn set_back_info(&mut self, info: Self::InfoType) {
self.info = info;
}

fn after_krate(&mut self) -> Result<(), Error> {
Expand Down Expand Up @@ -775,8 +780,8 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
// External crates will provide links to these structures, so
// these modules are recursed into, but not rendered normally
// (a flag on the context).
if !self.render_redirect_pages {
self.render_redirect_pages = item.is_stripped();
if !self.info.render_redirect_pages {
self.info.render_redirect_pages = item.is_stripped();
}
let item_name = item.name.unwrap();
self.dst.push(&*item_name.as_str());
Expand All @@ -793,19 +798,19 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
self.shared.fs.write(joint_dst, buf)?;
}
}
if !self.is_inside_inlined_module {
if !self.info.is_inside_inlined_module {
if let Some(def_id) = item.def_id()
&& self.cache().inlined_items.contains(&def_id)
{
self.is_inside_inlined_module = true;
self.info.is_inside_inlined_module = true;
}
} else if !self.cache().document_hidden && item.is_doc_hidden() {
// We're not inside an inlined module anymore since this one cannot be re-exported.
self.is_inside_inlined_module = false;
self.info.is_inside_inlined_module = false;
}

// Render sidebar-items.js used throughout this module.
if !self.render_redirect_pages {
if !self.info.render_redirect_pages {
let (clean::StrippedItem(box clean::ModuleItem(ref module))
| clean::ModuleItem(ref module)) = item.kind
else {
Expand Down Expand Up @@ -836,8 +841,8 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
// External crates will provide links to these structures, so
// these modules are recursed into, but not rendered normally
// (a flag on the context).
if !self.render_redirect_pages {
self.render_redirect_pages = item.is_stripped();
if !self.info.render_redirect_pages {
self.info.render_redirect_pages = item.is_stripped();
}

let buf = self.render_item(&item, false);
Expand All @@ -850,7 +855,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
let joint_dst = self.dst.join(file_name);
self.shared.fs.write(joint_dst, buf)?;

if !self.render_redirect_pages {
if !self.info.render_redirect_pages {
self.shared.all.borrow_mut().append(full_path(self, &item), &item_type);
}
// If the item is a macro, redirect from the old macro URL (with !)
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/html/render/print_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ pub(super) fn print_item(cx: &mut Context<'_>, item: &clean::Item, buf: &mut Buf
// this page, and this link will be auto-clicked. The `id` attribute is
// used to find the link to auto-click.
let src_href =
if cx.include_sources && !item.is_primitive() { cx.src_href(item) } else { None };
if cx.info.include_sources && !item.is_primitive() { cx.src_href(item) } else { None };

let path_components = if item.is_primitive() || item.is_keyword() {
vec![]
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pub(crate) fn write_shared(
&cx.shared.style_files,
cx.shared.layout.css_file_extension.as_deref(),
&cx.shared.resource_suffix,
cx.include_sources,
cx.info.include_sources,
)?;
match &opt.index_page {
Some(index_page) if opt.enable_index_page => {
Expand Down
4 changes: 2 additions & 2 deletions src/librustdoc/html/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ struct SourceCollector<'a, 'tcx> {

impl DocVisitor<'_> for SourceCollector<'_, '_> {
fn visit_item(&mut self, item: &clean::Item) {
if !self.cx.include_sources {
if !self.cx.info.include_sources {
return;
}

Expand All @@ -146,7 +146,7 @@ impl DocVisitor<'_> for SourceCollector<'_, '_> {
// something like that), so just don't include sources for the
// entire crate. The other option is maintaining this mapping on a
// per-file basis, but that's probably not worth it...
self.cx.include_sources = match self.emit_source(&filename, file_span) {
self.cx.info.include_sources = match self.emit_source(&filename, file_span) {
Ok(()) => true,
Err(e) => {
self.cx.shared.tcx.dcx().span_err(
Expand Down
6 changes: 3 additions & 3 deletions src/librustdoc/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
}

const RUN_ON_MODULE: bool = false;
type InfoType = ();

fn init(
krate: clean::Crate,
Expand All @@ -161,9 +162,8 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
))
}

fn make_child_renderer(&self) -> Self {
self.clone()
}
fn make_child_renderer(&mut self) -> Self::InfoType {}
fn set_back_info(&mut self, _info: Self::InfoType) {}

/// Inserts an item into the index. This should be used rather than directly calling insert on
/// the hashmap because certain items (traits and types) need to have their mappings for trait
Expand Down

0 comments on commit 0d6f0cd

Please sign in to comment.