-**IR datatypes**:
+**IR data types**:
* allowing near-arbitrary SPIR-V instructions for any unrecognized opcodes
* IDs are replaced with interned/"entity" handles (see below)
* interning for attributes (decorations & similar), types and constants
@@ -71,7 +71,7 @@ With the initial focus being on [Rust-GPU]'s usecase, various (otherwise desirab
**Framework utilities**:
* `visit`/`transform`: immutable/mutable IR traversal
-* `print`: pretty-printer with (syntax-highlighted and hyperlinked) HTML output
+* `print`: pretty-printer with (styled and hyperlinked) HTML output
**Passes (to/from/on SPIR-🇹)**:
* `spv::lower`: "lowering" from SPIR-V, normalizing away many irrelevant details
diff --git a/src/cfg.rs b/src/cfg.rs
index 937a7af..ade72b2 100644
--- a/src/cfg.rs
+++ b/src/cfg.rs
@@ -10,7 +10,7 @@ use smallvec::SmallVec;
use std::mem;
/// The control-flow graph (CFG) of a function, as control-flow instructions
-/// (`ControlInst`s) attached to `ControlRegion`s, as an "action on exit", i.e.
+/// ([`ControlInst`]s) attached to [`ControlRegion`]s, as an "action on exit", i.e.
/// "terminator" (while intra-region control-flow is strictly structured).
#[derive(Clone, Default)]
pub struct ControlFlowGraph {
@@ -28,7 +28,7 @@ pub struct ControlInst {
// FIXME(eddyb) change the inline size of this to fit most instructions.
pub targets: SmallVec<[ControlRegion; 4]>,
- /// `target_inputs[region][input_idx]` is the `Value` that
+ /// `target_inputs[region][input_idx]` is the [`Value`] that
/// `Value::ControlRegionInput { region, input_idx }` will get on entry,
/// where `region` must be appear at least once in `targets` - this is a
/// separate map instead of being part of `targets` because it reflects the
@@ -68,7 +68,7 @@ pub enum ExitInvocationKind {
}
impl ControlFlowGraph {
- /// Iterate over all `ControlRegion`s making up `func_def_body`'s CFG, in
+ /// Iterate over all [`ControlRegion`]s making up `func_def_body`'s CFG, in
/// reverse post-order (RPO).
///
/// RPO iteration over a CFG provides certain guarantees, most importantly
@@ -95,10 +95,10 @@ impl ControlFlowGraph {
// HACK(eddyb) this only serves to disallow accessing `private_count` field of
// `IncomingEdgeCount`.
mod sealed {
- /// Opaque newtype for the count of incoming edges (into a `ControlRegion`).
+ /// Opaque newtype for the count of incoming edges (into a [`ControlRegion`](crate::ControlRegion)).
///
/// The private field prevents direct mutation or construction, forcing the
- /// use of `IncomingEdgeCount::ONE` and addition operations to produce some
+ /// use of [`IncomingEdgeCount::ONE`] and addition operations to produce some
/// specific count (which would require explicit workarounds for misuse).
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub(super) struct IncomingEdgeCount(usize);
@@ -183,10 +183,11 @@ impl ControlFlowGraph {
}
}
+#[allow(rustdoc::private_intra_doc_links)]
/// Control-flow "structurizer", which attempts to convert as much of the CFG
/// as possible into structural control-flow (regions).
///
-/// See `StructurizeRegionState`'s docs for more details on the algorithm.
+/// See [`StructurizeRegionState`]'s docs for more details on the algorithm.
//
// FIXME(eddyb) document this (instead of having it on `StructurizeRegionState`).
//
@@ -211,22 +212,22 @@ impl ControlFlowGraph {
pub struct Structurizer<'a> {
cx: &'a Context,
- /// Scrutinee type for `SelectionKind::BoolCond`.
+ /// Scrutinee type for [`SelectionKind::BoolCond`].
type_bool: Type,
- /// Scrutinee value for `SelectionKind::BoolCond`, for the "then" case.
+ /// Scrutinee value for [`SelectionKind::BoolCond`], for the "then" case.
const_true: Const,
- /// Scrutinee value for `SelectionKind::BoolCond`, for the "else" case.
+ /// Scrutinee value for [`SelectionKind::BoolCond`], for the "else" case.
const_false: Const,
func_def_body: &'a mut FuncDefBody,
incoming_edge_counts: EntityOrientedDenseMap,
- /// Keyed by the input to `structurize_region_from` (the start `ControlRegion`),
+ /// Keyed by the input to `structurize_region_from` (the start [`ControlRegion`]),
/// and describing the state of that partial structurization step.
///
- /// See also `StructurizeRegionState`'s docs.
+ /// See also [`StructurizeRegionState`]'s docs.
//
// FIXME(eddyb) use `EntityOrientedDenseMap` (which lacks iteration by design).
structurize_region_state: FxIndexMap,
@@ -239,10 +240,10 @@ pub struct Structurizer<'a> {
}
/// The state of one `structurize_region_from` invocation (keyed on its start
-/// `ControlRegion` in `Structurizer`) and its `PartialControlRegion` output.
+/// [`ControlRegion`] in [`Structurizer`]) and its [`PartialControlRegion`] output.
///
/// There is a fourth (or 0th) implicit state, which is where nothing has yet
-/// observed some region, and `Structurizer` isn't tracking it at all.
+/// observed some region, and [`Structurizer`] isn't tracking it at all.
//
// FIXME(eddyb) make the 0th state explicit and move `incoming_edge_counts` to it.
enum StructurizeRegionState {
@@ -251,8 +252,8 @@ enum StructurizeRegionState {
/// Structurization completed, and this region can now be claimed.
Ready {
- /// If this region had backedges (targeting its start `ControlRegion`),
- /// their bundle is taken from the region's `DeferredEdgeBundleSet`,
+ /// If this region had backedges (targeting its start [`ControlRegion`]),
+ /// their bundle is taken from the region's [`DeferredEdgeBundleSet`],
/// and kept in this field instead (for simpler/faster access).
///
/// Claiming a region with backedges can combine them with the bundled
@@ -264,27 +265,27 @@ enum StructurizeRegionState {
region: PartialControlRegion,
},
- /// Region was claimed (by an `IncomingEdgeBundle`, with the appropriate
- /// total `IncomingEdgeCount`, minus any `consumed_backedges`), and has
+ /// Region was claimed (by an [`IncomingEdgeBundle`], with the appropriate
+ /// total [`IncomingEdgeCount`], minus any `consumed_backedges`), and has
/// since likely been incorporated as part of some larger region.
Claimed,
}
/// An "(incoming) edge bundle" is a subset of the edges into a single `target`.
///
-/// When `accumulated_count` reaches the total `IncomingEdgeCount` for `target`,
-/// that `IncomingEdgeBundle` is said to "effectively own" its `target` (akin to
+/// When `accumulated_count` reaches the total [`IncomingEdgeCount`] for `target`,
+/// that [`IncomingEdgeBundle`] is said to "effectively own" its `target` (akin to
/// the more commonly used CFG domination relation, but more "incremental").
struct IncomingEdgeBundle {
target: ControlRegion,
accumulated_count: IncomingEdgeCount,
- /// The `Value`s that `Value::ControlRegionInput { region, .. }` will get
+ /// The [`Value`]s that `Value::ControlRegionInput { region, .. }` will get
/// on entry into `region`, through this "edge bundle".
target_inputs: SmallVec<[Value; 2]>,
}
-/// A "deferred (incoming) edge bundle" is an `IncomingEdgeBundle` that cannot
+/// A "deferred (incoming) edge bundle" is an [`IncomingEdgeBundle`] that cannot
/// be structurized immediately, but instead waits for its `accumulated_count`
/// to reach the full count of its `target`, before it can grafted into some
/// structured control-flow region.
@@ -315,7 +316,7 @@ struct IncomingEdgeBundle {
/// branch => label1
/// }
/// ```
-/// which could theoretically be simplified (after the `Structurizer`) to:
+/// which could theoretically be simplified (after the [`Structurizer`]) to:
/// ```text
/// label1_condition = a | b
/// if label1_condition {
@@ -327,7 +328,7 @@ struct DeferredEdgeBundle {
edge_bundle: IncomingEdgeBundle,
}
-/// Set of `DeferredEdgeBundle`s, uniquely keyed by their `target`s.
+/// Set of [`DeferredEdgeBundle`]s, uniquely keyed by their `target`s.
struct DeferredEdgeBundleSet {
// FIXME(eddyb) this field requires this invariant to be maintained:
// `target_to_deferred[target].edge_bundle.target == target` - but that's
@@ -335,25 +336,25 @@ struct DeferredEdgeBundleSet {
target_to_deferred: FxIndexMap,
}
-/// Partially structurized `ControlRegion`, the result of combining together
-/// several smaller `ControlRegion`s, based on CFG edges between them.
+/// Partially structurized [`ControlRegion`], the result of combining together
+/// several smaller [`ControlRegion`]s, based on CFG edges between them.
struct PartialControlRegion {
// FIXME(eddyb) keep this in the original `ControlRegion` instead.
children: EntityList,
- /// When not all transitive targets could be claimed into the `ControlRegion`,
+ /// When not all transitive targets could be claimed into the [`ControlRegion`],
/// some remain as deferred exits, blocking further structurization until
/// all other edges to those targets are gathered together.
///
/// If both `deferred_edges` is empty and `deferred_return` is `None`, then
- /// the `ControlRegion` never exits, i.e. it has divergent control-flow
+ /// the [`ControlRegion`] never exits, i.e. it has divergent control-flow
/// (such as an infinite loop).
deferred_edges: DeferredEdgeBundleSet,
/// Structured "return" out of the function (holding `output`s for the
- /// function body, i.e. the inputs to the `ControlInstKind::Return`).
+ /// function body, i.e. the inputs to the [`ControlInstKind::Return`]).
///
- /// Unlike `DeferredEdgeBundle`, this doesn't need a condition, as it's
+ /// Unlike [`DeferredEdgeBundle`], this doesn't need a condition, as it's
/// effectively a "fallback", only used when `deferred_edges` is empty.
deferred_return: Option>,
}
@@ -636,13 +637,13 @@ impl<'a> Structurizer<'a> {
}
/// Structurize a region starting from `unstructured_region`, and extending
- /// it (by combining the smaller `ControlRegion`s) as much as possible into
+ /// it (by combining the smaller [`ControlRegion`]s) as much as possible into
/// the CFG (likely everything dominated by `unstructured_region`).
///
/// The output of this process is stored in, and any other bookkeeping is
/// done through, `self.structurize_region_state[unstructured_region]`.
///
- /// See also `StructurizeRegionState`'s docs.
+ /// See also [`StructurizeRegionState`]'s docs.
fn structurize_region_from(&mut self, unstructured_region: ControlRegion) {
{
let old_state = self
@@ -673,7 +674,7 @@ impl<'a> Structurizer<'a> {
`ControlInst` (CFG wasn't unstructured in the first place?)",
);
- /// Marker error type for unhandled `ControlInst`s below.
+ /// Marker error type for unhandled [`ControlInst`]s below.
struct UnsupportedControlInst(ControlInst);
let region_from_control_inst = {
@@ -910,7 +911,7 @@ impl<'a> Structurizer<'a> {
}
}
- /// Build a `Select` `ControlNode`, from partially structured `cases`,
+ /// Build a `Select` [`ControlNode`], from partially structured `cases`,
/// merging all of their `deferred_{edges,returns}` together.
fn structurize_select(
&mut self,
@@ -1170,7 +1171,7 @@ impl<'a> Structurizer<'a> {
}
/// When structurization is only partial, and there remain unclaimed regions,
- /// they have to be reintegrated into the CFG, putting back `ControlInst`s
+ /// they have to be reintegrated into the CFG, putting back [`ControlInst`]s
/// where `structurize_region_from` has taken them from.
///
/// This function handles one region at a time to make it more manageable,
diff --git a/src/context.rs b/src/context.rs
index 00932fb..35aef36 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -1,3 +1,5 @@
+//! [`Context`](struct.Context.html) and related types/traits.
+
use rustc_hash::FxHashMap;
use std::hash::Hash;
use std::mem;
@@ -9,10 +11,10 @@ use std::ops::{Deref, DerefMut};
/// Those resources currently are:
/// * interners, for anything without an identity, and which can be deduplicated
/// * "entity" allocators, for everything else - i.e. anything with an identity
-/// that needs to remain unique across an entire `Context`
-/// * the *definition* of an entity isn't kept in the `Context`, but rather in
-/// some `EntityDefs` collection somewhere in a `Module` (or further nested),
-/// with only the entity *indices* being allocated by the `Context`
+/// that needs to remain unique across an entire [`Context`]
+/// * the *definition* of an entity isn't kept in the [`Context`], but rather in
+/// some [`EntityDefs`] collection somewhere in a [`Module`](crate::Module) (or further nested),
+/// with only the entity *indices* being allocated by the [`Context`]
#[derive(Default)]
pub struct Context {
interners: Interners,
@@ -160,15 +162,18 @@ impl std::ops::Index for Context {
}
}
-/// Collection holding the actual definitions for `Context`-allocated entities.
+// FIXME(eddyb) consider including `Rc` in `EntityDefs` to avoid having
+// to pass it manually to the `EntityDefs::define` methods (which feels dangerous!).
+//
+/// Collection holding the actual definitions for [`Context`]-allocated entities.
///
-/// By design there is no way to iterate the contents of an `EntityDefs`, or
-/// generate entity indices without defining the entity in an `EntityDefs`.
+/// By design there is no way to iterate the contents of an [`EntityDefs`], or
+/// generate entity indices without defining the entity in an [`EntityDefs`].
#[derive(Clone)]
pub struct EntityDefs {
/// Entities are grouped into chunks, with per-entity-type chunk sizes
/// (powers of 2) specified via `entities!` below.
- /// This allows different `EntityDefs`s to independently define more
+ /// This allows different [`EntityDefs`]s to independently define more
/// entities, without losing compactness (until a whole chunk is filled).
//
// FIXME(eddyb) consider using `u32` instead of `usize` for the "flattened base".
@@ -180,7 +185,7 @@ pub struct EntityDefs {
/// defining more entities into it, without allocating new chunks).
incomplete_chunk_start_and_flattened_base: Option<(E, usize)>,
- /// All chunks' definitions are flattened into one contiguous `Vec`, where
+ /// All chunks' definitions are flattened into one contiguous [`Vec`], where
/// the start of each chunk's definitions in `flattened` is indicated by
/// either `complete_chunk_start_to_flattened_base` (for completed chunks)
/// or `incomplete_chunk_start_and_flattened_base`.
@@ -303,16 +308,16 @@ impl EntityOrientedMapKey for E {
/// Map with `K` keys and `V` values, that is:
/// * "entity-oriented" `K` keys, i.e. that are or contain exactly one entity
-/// (supported via `K: EntityOrientedMapKey` for extensibility)
+/// (supported via [`K: EntityOrientedMapKey`](EntityOrientedMapKey) for extensibility)
/// * "dense" in the sense of few (or no) gaps in (the entities in) its keys
-/// (relative to the entities defined in the corresponding `EntityDefs`)
+/// (relative to the entities defined in the corresponding [`EntityDefs`])
///
-/// By design there is no way to iterate the entries in an `EntityOrientedDenseMap`.
+/// By design there is no way to iterate the entries in an [`EntityOrientedDenseMap`].
//
// FIXME(eddyb) implement a "sparse" version as well, and maybe some bitsets?
#[derive(Clone)]
pub struct EntityOrientedDenseMap, V> {
- /// Like in `EntityDefs`, entities are grouped into chunks, but there is no
+ /// Like in [`EntityDefs`], entities are grouped into chunks, but there is no
/// flattening, since arbitrary insertion orders have to be supported.
chunk_start_to_value_slots: SmallFxHashMap>,
}
@@ -458,8 +463,9 @@ impl, V> std::ops::IndexMut for EntityOrientedDens
}
}
+#[allow(rustdoc::private_intra_doc_links)]
/// Doubly-linked list, "intrusively" going through `E::Def`, which must be an
-/// `EntityListNode` (to hold the "previous/next node" links).
+/// [`EntityListNode`] (to hold the "previous/next node" links).
///
/// Fields are private to avoid arbitrary user interactions.
#[derive(Copy, Clone)]
@@ -604,7 +610,7 @@ impl>, D> EntityList {
}
}
-/// `EntityList` iterator, but with a different API than `Iterator`.
+/// [`EntityList`] iterator, but with a different API than [`Iterator`].
///
/// This can also be considered a (non-random-access) "subslice" of the list.
#[derive(Copy, Clone)]
@@ -657,11 +663,11 @@ impl>, D> EntityListIter {
}
}
-/// `EntityList` node, containing the "intrusive" list links, and the rest of
+/// [`EntityList`] node, containing the "intrusive" list links, and the rest of
/// the entity definition (the `inner_def` field of type `D`).
///
/// Fields are private to avoid arbitrary user interactions outside of special
-/// methods and `Deref`/`DerefMut`.
+/// methods and [`Deref`]/[`DerefMut`].
//
// FIXME(eddyb) `Deref`/`DerefMut` aren't the best API, could this be hidden
// further by making `EntityDefs` hide the list links in the `Index` impl?
@@ -729,7 +735,7 @@ macro_rules! interners {
pub struct $name(
// FIXME(eddyb) figure out how to sneak niches into these types, to
// allow e.g. `Option` around them to not increase the size.
- u32,
+ #[doc(hidden)] u32,
);
$(impl Default for $name {
@@ -815,7 +821,7 @@ macro_rules! entities {
// NOTE(eddyb) never derive `PartialOrd, Ord` for these types, as
// observing the entity index allocation order shouldn't be allowed.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
- pub struct $name(NonZeroU32);
+ pub struct $name(#[doc(hidden)] NonZeroU32);
impl sealed::Entity for $name {
type Def = $def;
diff --git a/src/func_at.rs b/src/func_at.rs
index d7bdf2d..f5e1e62 100644
--- a/src/func_at.rs
+++ b/src/func_at.rs
@@ -1,11 +1,11 @@
//! Traversal helpers for intra-function entities.
//!
-//! `FuncAt `/`FuncAtMut ` are like `(&FuncDefBody, P)`/`(&mut FuncDefBody, P`)
+//! [`FuncAt `]/[`FuncAtMut `] are like `(&FuncDefBody, P)`/`(&mut FuncDefBody, P`)
//! (where `P` is some type describing a "position" in the function), except:
-//! * they only borrow the `EntityDefs` fields of `FuncDefBody`
+//! * they only borrow the [`EntityDefs`] fields of [`FuncDefBody`]
//! * this can prevent borrow conflicts, especially when mutating other fields
//! * it also avoids accidentally accessing parts of the function definition
-//! without going through `P` (as `EntityDefs` requires keys for any access)
+//! without going through `P` (as [`EntityDefs`] requires keys for any access)
//! * they're dedicated types with inherent methods and trait `impl`s
// NOTE(eddyb) wrong wrt lifetimes (https://github.com/rust-lang/rust-clippy/issues/5004).
@@ -19,7 +19,7 @@ use crate::{
/// Immutable traversal (i.e. visiting) helper for intra-function entities.
///
/// The point/position type `P` should be an entity or a shallow entity wrapper
-/// (e.g. `EntityList`).
+/// (e.g. [`EntityList`]).
#[derive(Copy, Clone)]
pub struct FuncAt<'a, P: Copy> {
pub control_regions: &'a EntityDefs,
@@ -98,7 +98,7 @@ impl<'a> FuncAt<'a, DataInst> {
}
impl FuncAt<'_, Value> {
- /// Return the `Type` of this `Value` (`Context` used for `Value::Const`).
+ /// Return the [`Type`] of this [`Value`] ([`Context`] used for [`Value::Const`]).
pub fn type_of(self, cx: &Context) -> Type {
match self.position {
Value::Const(ct) => cx[ct].ty,
@@ -117,7 +117,7 @@ impl FuncAt<'_, Value> {
/// Mutable traversal (i.e. transforming) helper for intra-function entities.
///
/// The point/position type `P` should be an entity or a shallow entity wrapper
-/// (e.g. `EntityList`).
+/// (e.g. [`EntityList`]).
pub struct FuncAtMut<'a, P: Copy> {
pub control_regions: &'a mut EntityDefs,
pub control_nodes: &'a mut EntityDefs,
diff --git a/src/lib.rs b/src/lib.rs
index f4f33c9..c49043c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,13 +1,63 @@
-//! # `SPIR-🇹`
+//! >
+//! >
+//! >
+//! > ## `SPIR-🇹`
+//! >
+//! > **⋯🢒 🇹arget 🠆 🇹ransform 🠆 🇹ranslate ⋯🢒**
+//! >
+//! >
+//! >
+//! > **SPIR-🇹** is a research project aimed at exploring shader-oriented IR designs
+//! > derived from SPIR-V, and producing a framework around such an IR to facilitate
+//! > advanced compilation pipelines, beyond what existing SPIR-V tooling allows for.
+//! >
+//! > 🚧 *This project is in active design and development, many details can and will change* 🚧
+//! >
+//! >
+//! >
+//! > *—
+#![cfg_attr(
+ docsrs,
+ // NOTE(eddyb) this requires updating `repository` before every release to
+ // end in `/tree/` followed by the tag name, in order to be useful.
+ doc = concat!(
+ "[`", env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), "`'s `README`]",
+ "(", env!("CARGO_PKG_REPOSITORY"), "#readme)* "
+ )
+)]
+#![cfg_attr(
+ git_main_docs,
+ doc = concat!(
+ "[`", env!("CARGO_PKG_NAME"), " @ ", env!("GIT_MAIN_DESCRIBE"), "`'s `README`]",
+ "(https://github.com/EmbarkStudios/spirt/tree/", env!("GIT_MAIN_COMMIT"), "#readme)* "
+ )
+)]
+#![cfg_attr(
+ any(docsrs, git_main_docs),
+ doc = " *(click through for the full version)*"
+)]
+// HACK(eddyb) this is only relevant for local builds (which don't need a link).
+#![cfg_attr(
+ not(any(docsrs, git_main_docs)),
+ doc = concat!("`", env!("CARGO_PKG_NAME"), "`'s `README`* ")
+)]
//!
-//! **⋯🢒 🇹arget 🠆 🇹ransform 🠆 🇹ranslate ⋯🢒**
+//! *Check out also [the `EmbarkStudios/spirt` GitHub repository](https://github.com/EmbarkStudios/spirt),
+//! for any additional developments.*
//!
-//! Shader-focused IR to facilitate working with SPIR-V in a compiler setting.
+//! #### Notable types/modules
+//!
+//! ##### IR data types
+// HACK(eddyb) using `(struct.Context.html)` to link `Context`, not `context::Context`.
+//! * [`Context`](struct.Context.html): handles interning ([`Type`]s, [`Const`]s, etc.) and allocating entity handles
+//! * [`Module`]: owns [`Func`]s and [`GlobalVar`]s (rooted by [`exports`](Module::exports))
+//! * [`FuncDefBody`]: owns [`ControlRegion`]s and [DataInst]s (rooted by [`body`](FuncDefBody::body))
+//!
+//! ##### Utilities and passes
+//! * [`print`](mod@print): pretty-printer with (styled and hyperlinked) HTML output
+//! * [`spv::lower`]/[`spv::lift`]: conversion from/to SPIR-V
+//! * [`cfg::Structurizer`]: (re)structurization from arbitrary control-flow
//!
-//! 🚧 *This project is in active design and development, check out
-//! [the GitHub repository](https://github.com/EmbarkStudios/spirt).* 🚧
-
-// FIXME(eddyb) should crate docs use `#[doc = include!("../README.md")]`?
// BEGIN - Embark standard lints v6 for Rust 1.55+
// do not change or add/remove here, but one can add exceptions after this section
@@ -100,36 +150,46 @@
// we almost never need `unsafe` code and this is a further "speed bump" to it.
#![forbid(unsafe_code)]
-use smallvec::SmallVec;
-use std::collections::BTreeSet;
-
-// HACK(eddyb) work around the lack of `FxIndex{Map,Set}` type aliases elsewhere.
-type FxIndexMap =
- indexmap::IndexMap>;
-type FxIndexSet = indexmap::IndexSet>;
-
-mod context;
-pub use context::{
- AttrSet, Const, Context, ControlNode, ControlRegion, DataInst, EntityDefs, EntityList,
- EntityListIter, EntityOrientedDenseMap, EntityOrientedMapKey, Func, GlobalVar, InternedStr,
- Type,
-};
-
+// NOTE(eddyb) all the modules are declared here, but they're documented "inside"
+// (i.e. using inner doc comments).
pub mod cfg;
+mod context;
pub mod func_at;
pub mod print;
pub mod transform;
pub mod visit;
pub mod passes {
+ //! IR transformations (typically whole-[`Module`](crate::Module)).
+ //
// NOTE(eddyb) inline `mod` to avoid adding APIs here, it's just namespacing.
pub mod legalize;
pub mod link;
}
-
pub mod spv;
+use smallvec::SmallVec;
+use std::collections::BTreeSet;
+
+// HACK(eddyb) work around the lack of `FxIndex{Map,Set}` type aliases elsewhere.
+#[doc(hidden)]
+type FxIndexMap =
+ indexmap::IndexMap>;
+#[doc(hidden)]
+type FxIndexSet = indexmap::IndexSet>;
+
+// NOTE(eddyb) these reexports are all documented inside `context`.
+// FIXME(eddyb) maybe make an `entity` module to move either the definitions,
+// or at least the re-exports - an `ir` module might help too, organizationally?
+pub use context::{
+ Context, EntityDefs, EntityList, EntityListIter, EntityOrientedDenseMap, EntityOrientedMapKey,
+};
+
+/// Interned handle for a [`str`].
+pub use context::InternedStr;
+
// HACK(eddyb) this only serves to disallow modifying the `cx` field of `Module`.
+#[doc(hidden)]
mod sealed {
use super::*;
use std::rc::Rc;
@@ -140,7 +200,7 @@ mod sealed {
///
/// Notable choices made for this field:
/// * private to disallow switching the context of a module
- /// * `Rc` sharing to allow multiple modules to use the same context
+ /// * [`Rc`] sharing to allow multiple modules to use the same context
/// (`Context: !Sync` because of the interners so it can't be `Arc`)
cx: Rc,
@@ -181,11 +241,14 @@ mod sealed {
}
pub use sealed::Module;
+/// Semantic properties of a SPIR-T module (not tied to any declarations/definitions).
#[derive(Clone)]
pub enum ModuleDialect {
Spv(spv::Dialect),
}
+/// Non-semantic details (i.e. debuginfo) of a SPIR-Y module (not tied to any
+/// declarations/definitions).
#[derive(Clone)]
pub enum ModuleDebugInfo {
Spv(spv::ModuleDebugInfo),
@@ -203,13 +266,18 @@ pub enum ExportKey {
},
}
-/// A definition exported out of a module (see also `ExportKey`).
+/// A definition exported out of a module (see also [`ExportKey`]).
#[derive(Copy, Clone)]
pub enum Exportee {
GlobalVar(GlobalVar),
Func(Func),
}
+/// Interned handle for an [`AttrSetDef`](crate::AttrSetDef)
+/// (a set of [`Attr`](crate::Attr)s).
+pub use context::AttrSet;
+
+/// Definition for an [`AttrSet`]: a set of [`Attr`]s.
#[derive(Default, PartialEq, Eq, Hash)]
pub struct AttrSetDef {
// FIXME(eddyb) use `BTreeMap` and split some of the params
@@ -222,6 +290,11 @@ pub struct AttrSetDef {
pub attrs: BTreeSet,
}
+/// Any semantic or non-semantic (debuginfo) decoration/modifier, that can be
+/// *optionally* applied to some declaration/definition.
+///
+/// Always used via [`AttrSetDef`] (interned as [`AttrSet`]).
+//
// FIXME(eddyb) consider interning individual attrs, not just `AttrSet`s.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Attr {
@@ -239,8 +312,12 @@ pub enum Attr {
SpvBitflagsOperand(spv::Imm),
}
-// HACK(eddyb) wrapper to limit `Ord` for interned index types (e.g. `InternedStr`)
-// to only situations where the interned index reflects contents (i.e. equality).
+/// Wrapper to limit `Ord` for interned index types (e.g. [`InternedStr`])
+/// to only situations where the interned index reflects contents (i.e. equality).
+//
+// FIXME(eddyb) this is not ideal, and it might be more useful to replace the
+// `BTreeSet` with an `BTreeMap`, where only `Attr` needs
+// to be `Ord`, and the details that cannot be `Ord`, can be moved to `AttrValue`.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct OrdAssertEq(pub T);
@@ -262,6 +339,11 @@ impl Ord for OrdAssertEq {
}
}
+/// Interned handle for a [`TypeDef`](crate::TypeDef).
+pub use context::Type;
+
+/// Definition for a [`Type`].
+//
// FIXME(eddyb) maybe special-case some basic types like integers.
#[derive(PartialEq, Eq, Hash)]
pub struct TypeDef {
@@ -270,11 +352,12 @@ pub struct TypeDef {
pub ctor_args: SmallVec<[TypeCtorArg; 2]>,
}
+/// [`Type`] "constructor": a [`TypeDef`] wiithout any [`TypeCtorArg`]s ([`Type`]s/[`Const`]s).
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum TypeCtor {
SpvInst(spv::Inst),
- /// The type of a `ConstCtor::SpvStringLiteralForExtInst` constant, i.e.
+ /// The type of a [`ConstCtor::SpvStringLiteralForExtInst`] constant, i.e.
/// a SPIR-V `OpString` with no actual type in SPIR-V.
SpvStringLiteralForExtInst,
}
@@ -285,6 +368,11 @@ pub enum TypeCtorArg {
Const(Const),
}
+/// Interned handle for a [`ConstDef`](crate::ConstDef) (a constant value).
+pub use context::Const;
+
+/// Definition for a [`Const`]: a constant value.
+//
// FIXME(eddyb) maybe special-case some basic consts like integer literals.
#[derive(PartialEq, Eq, Hash)]
pub struct ConstDef {
@@ -294,6 +382,7 @@ pub struct ConstDef {
pub ctor_args: SmallVec<[Const; 2]>,
}
+/// [`Const`] "constructor": a [`ConstDef`] wiithout any nested [`Const`]s.
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum ConstCtor {
PtrToGlobalVar(GlobalVar),
@@ -306,7 +395,7 @@ pub enum ConstCtor {
SpvStringLiteralForExtInst(InternedStr),
}
-/// Declarations (`GlobalVarDecl`, `FuncDecl`) can contain a full definition,
+/// Declarations ([`GlobalVarDecl`], [`FuncDecl`]) can contain a full definition,
/// or only be an import of a definition (e.g. from another module).
#[derive(Clone)]
pub enum DeclDef {
@@ -320,6 +409,11 @@ pub enum Import {
LinkName(InternedStr),
}
+/// Entity handle for a [`GlobalVarDecl`](crate::GlobalVarDecl) (a global variable).
+pub use context::GlobalVar;
+
+/// Declaration/definition for a [`GlobalVar`]: a global variable.
+//
// FIXME(eddyb) mark any `GlobalVar` not *controlled* by the SPIR-V module
// (roughly: storage classes that don't allow initializers, i.e. most of them),
// as an "import" from "the shader interface", and therefore "externally visible",
@@ -344,12 +438,17 @@ pub enum AddrSpace {
SpvStorageClass(u32),
}
+/// The body of a [`GlobalVar`] definition.
#[derive(Clone)]
pub struct GlobalVarDefBody {
/// If `Some`, the global variable will start out with the specified value.
pub initializer: Option,
}
+/// Entity handle for a [`FuncDecl`](crate::FuncDecl) (a function).
+pub use context::Func;
+
+/// Declaration/definition for a [`Func`]: a function.
#[derive(Clone)]
pub struct FuncDecl {
pub attrs: AttrSet,
@@ -368,6 +467,8 @@ pub struct FuncParam {
pub ty: Type,
}
+/// The body of a [`Func`] definition.
+//
// FIXME(eddyb) `FuncDefBody`/`func_def_body` are too long, find shorter names.
#[derive(Clone)]
pub struct FuncDefBody {
@@ -375,7 +476,7 @@ pub struct FuncDefBody {
pub control_nodes: EntityDefs,
pub data_insts: EntityDefs,
- /// The `ControlRegion` representing the whole body of the function.
+ /// The [`ControlRegion`] representing the whole body of the function.
///
/// Function parameters are provided via `body.inputs`, i.e. they can be
/// only accessed with `Value::ControlRegionInputs { region: body, idx }`.
@@ -395,33 +496,38 @@ pub struct FuncDefBody {
pub unstructured_cfg: Option,
}
-/// Linear chain of `ControlNode`s, describing a single-entry single-exit (SESE)
-/// control-flow "region" (subgraph) in a function's control-flow graph (CFG).
+/// Entity handle for a [`ControlRegionDef`](crate::ControlRegionDef)
+/// (a control-flow region).
+///
+/// A [`ControlRegion`] ("control-flow region") is a linear chain of [`ControlNode`]s,
+/// describing a single-entry single-exit (SESE) control-flow "region" (subgraph)
+/// in a function's control-flow graph (CFG).
///
/// # Control-flow
///
/// In SPIR-T, two forms of control-flow are used:
-/// * "structured": `ControlRegion`s and `ControlNode`s in a "mutual tree"
-/// * i.e. each such `ControlRegion` can only appear in exactly one `ControlNode`,
-/// and each `ControlNode` can only appear in exactly one `ControlRegion`
-/// * a region is either the function's body, or used as part of `ControlNode`
+/// * "structured": [`ControlRegion`]s and [`ControlNode`]s in a "mutual tree"
+/// * i.e. each such [`ControlRegion`] can only appear in exactly one [`ControlNode`],
+/// and each [`ControlNode`] can only appear in exactly one [`ControlRegion`]
+/// * a region is either the function's body, or used as part of [`ControlNode`]
/// (e.g. the "then" case of an `if`-`else`), itself part of a larger region
/// * when inside a region, reaching any other part of the function (or any
/// other function on call stack) requires leaving through the region's
/// single exit (also called "merge") point, i.e. its execution is either:
/// * "convergent": the region completes and continues into its parent
-/// `ControlNode`, or function (the latter being a "structured return")
+/// [`ControlNode`], or function (the latter being a "structured return")
/// * "divergent": execution gets stuck in the region (an infinite loop),
/// or is aborted (e.g. `OpTerminateInvocation` from SPIR-V)
-/// * "unstructured": `ControlRegion`s which connect to other `ControlRegion`s
-/// using `cfg::ControlInst`s (as described by a `cfg::ControlFlowGraph`)
+/// * "unstructured": [`ControlRegion`]s which connect to other [`ControlRegion`]s
+/// using [`cfg::ControlInst`](crate::cfg::ControlInst)s (as described by a
+/// [`cfg::ControlFlowGraph`](crate::cfg::ControlFlowGraph))
///
-/// When a function's entire body can be described by a single `ControlRegion`,
+/// When a function's entire body can be described by a single [`ControlRegion`],
/// that function is said to have (entirely) "structured control-flow".
///
/// Mixing "structured" and "unstructured" control-flow is supported because:
/// * during structurization, it allows structured subgraphs to remain connected
-/// by the same CFG edges that were connecting smaller `ControlRegion`s before
+/// by the same CFG edges that were connecting smaller [`ControlRegion`]s before
/// * structurization doesn't have to fail in the cases it doesn't fully support
/// yet, but can instead result in a "maximally structured" function
///
@@ -438,22 +544,22 @@ pub struct FuncDefBody {
///
/// # Data-flow interactions
///
-/// SPIR-T `Value`s follow "single static assignment" (SSA), just like SPIR-V:
+/// SPIR-T [`Value`](crate::Value)s follow "single static assignment" (SSA), just like SPIR-V:
/// * inside a function, any new value is produced (or "defined") as an output
-/// of `DataInst`/`ControlNode`, and "uses" of that value are `Value`s
-/// variants which refer to the defining `DataInst`/`ControlNode` directly
+/// of [`DataInst`]/[`ControlNode`], and "uses" of that value are [`Value`](crate::Value)s
+/// variants which refer to the defining [`DataInst`]/[`ControlNode`] directly
/// (guaranteeing the "single" and "static" of "SSA", by construction)
/// * the definition of a value must "dominate" all of its uses
/// (i.e. in all possible execution paths, the definition precedes all uses)
///
/// But unlike SPIR-V, SPIR-T's structured control-flow has implications for SSA:
-/// * dominance is simpler, so values defined in a `ControlRegion` can be used:
+/// * dominance is simpler, so values defined in a [`ControlRegion`](crate::ControlRegion) can be used:
/// * later in that region, including in the region's `outputs`
/// (which allows "exporting" values out to the rest of the function)
-/// * outside that region, but *only* if the parent `ControlNode` only has
+/// * outside that region, but *only* if the parent [`ControlNode`](crate::ControlNode) only has
/// exactly one child region (i.e. a single-case `Select`, or a `Loop`)
/// * this is an "emergent" property, stemming from the region having to
-/// execute (at least once) before the parent `ControlNode` can complete,
+/// execute (at least once) before the parent [`ControlNode`](crate::ControlNode) can complete,
/// but is not is not ideal (especially for reasoning about loops) and
/// should eventually be replaced with passing all such values through
/// the region `outputs` (or by inlining the region, in the `Select` case)
@@ -467,25 +573,28 @@ pub struct FuncDefBody {
/// instead of in the merge (where phi nodes require special-casing, as
/// their "uses" of all the "source" values would normally be illegal)
/// * in unstructured control-flow, region `inputs` are additionally used for
-/// phi nodes, as `cfg:ControlInst`s passing values to their target regions
+/// phi nodes, as [`cfg::ControlInst`](crate::cfg::ControlInst)s passing values to their target regions
+pub use context::ControlRegion;
+
+/// Definition for a [`ControlRegion`]: a control-flow region.
#[derive(Clone)]
pub struct ControlRegionDef {
- /// Inputs to this `ControlRegion`:
- /// * accessed using `Value::ControlRegionInput`
+ /// Inputs to this [`ControlRegion`]:
+ /// * accessed using [`Value::ControlRegionInput`]
/// * values provided by the parent:
/// * when this is the function body: the function's parameters
pub inputs: SmallVec<[ControlRegionInputDecl; 2]>,
pub children: EntityList,
- /// Output values from this `ControlRegion`, provided to the parent:
+ /// Output values from this [`ControlRegion`], provided to the parent:
/// * when this is the function body: these are the structured return values
/// * when this is a `Select` case: these are the values for the parent
- /// `ControlNode`'s outputs (accessed using `Value::ControlNodeOutput`)
+ /// [`ControlNode`]'s outputs (accessed using [`Value::ControlNodeOutput`])
/// * when this is a `Loop` body: these are the values to be used for the
/// next loop iteration's body `inputs`
- /// * **not** accessible through `Value::ControlNodeOutput` on the `Loop`,
- /// as it's both confusing regarding `Value::ControlRegionInput`, and
+ /// * **not** accessible through [`Value::ControlNodeOutput`] on the `Loop`,
+ /// as it's both confusing regarding [`Value::ControlRegionInput`], and
/// also there's nothing stopping body-defined values from directly being
/// used outside the loop (once that changes, this aspect can be flipped)
pub outputs: SmallVec<[Value; 2]>,
@@ -498,14 +607,23 @@ pub struct ControlRegionInputDecl {
pub ty: Type,
}
+/// Entity handle for a [`ControlNodeDef`](crate::ControlNodeDef)
+/// (a control-flow operator or leaf).
+///
+/// See [`ControlRegion`] docs for more on control-flow in SPIR-T.
+pub use context::ControlNode;
+
+/// Definition for a [`ControlNode`]: a control-flow operator or leaf.
+///
+/// See [`ControlRegion`] docs for more on control-flow in SPIR-T.
#[derive(Clone)]
pub struct ControlNodeDef {
pub kind: ControlNodeKind,
- /// Outputs from this `ControlNode`:
- /// * accessed using `Value::ControlNodeOutput`
+ /// Outputs from this [`ControlNode`]:
+ /// * accessed using [`Value::ControlNodeOutput`]
/// * values provided by `region.outputs`, where `region` is the executed
- /// child `ControlRegion`:
+ /// child [`ControlRegion`]:
/// * when this is a `Select`: the case that was chosen
pub outputs: SmallVec<[ControlNodeOutputDecl; 2]>,
}
@@ -519,18 +637,18 @@ pub struct ControlNodeOutputDecl {
#[derive(Clone)]
pub enum ControlNodeKind {
- /// Linear chain of `DataInst`s, executing in sequence.
+ /// Linear chain of [`DataInst`]s, executing in sequence.
///
- /// This is only an optimization over keeping `DataInst`s in `ControlRegion`
- /// linear chains directly, or even merging `DataInst` with `ControlNode`.
+ /// This is only an optimization over keeping [`DataInst`]s in [`ControlRegion`]
+ /// linear chains directly, or even merging [`DataInst`] with [`ControlNode`].
Block {
// FIXME(eddyb) should empty blocks be allowed? should `DataInst`s be
// linked directly into the `ControlRegion` `children` list?
insts: EntityList,
},
- /// Choose one `ControlRegion` out of `cases` to execute, based on a single
- /// value input (`scrutinee`) interpreted according to `SelectionKind`.
+ /// Choose one [`ControlRegion`] out of `cases` to execute, based on a single
+ /// value input (`scrutinee`) interpreted according to [`SelectionKind`].
///
/// This corresponds to "gamma" (`γ`) nodes in (R)VSDG, though those are
/// sometimes limited only to a two-way selection on a boolean condition.
@@ -572,6 +690,10 @@ pub enum SelectionKind {
SpvInst(spv::Inst),
}
+/// Entity handle for a [`DataInstDef`](crate::DataInstDef) (an SSA instruction).
+pub use context::DataInst;
+
+/// Definition for a [`DataInst`]: an SSA instruction.
#[derive(Clone)]
pub struct DataInstDef {
pub attrs: AttrSet,
@@ -598,7 +720,7 @@ pub enum DataInstKind {
pub enum Value {
Const(Const),
- /// One of the inputs to a `ControlRegion`:
+ /// One of the inputs to a [`ControlRegion`]:
/// * declared by `region.inputs[input_idx]`
/// * value provided by the parent of the `region`:
/// * when `region` is the function body: `input_idx`th function parameter
@@ -607,16 +729,16 @@ pub enum Value {
input_idx: u32,
},
- /// One of the outputs produced by a `ControlNode`:
+ /// One of the outputs produced by a [`ControlNode`]:
/// * declared by `control_node.outputs[output_idx]`
/// * value provided by `region.outputs[output_idx]`, where `region` is the
- /// executed child `ControlRegion` (of `control_node`):
+ /// executed child [`ControlRegion`] (of `control_node`):
/// * when `control_node` is a `Select`: the case that was chosen
ControlNodeOutput {
control_node: ControlNode,
output_idx: u32,
},
- /// The output value of a `DataInst`.
+ /// The output value of a [`DataInst`].
DataInstOutput(DataInst),
}
diff --git a/src/passes/legalize.rs b/src/passes/legalize.rs
index 7393284..386b416 100644
--- a/src/passes/legalize.rs
+++ b/src/passes/legalize.rs
@@ -1,7 +1,7 @@
use crate::visit::{InnerVisit, Visitor};
use crate::{cfg, AttrSet, Const, Context, DeclDef, Func, FxIndexSet, GlobalVar, Module, Type};
-/// Apply the `cfg::Structurize` algorithm to all function definitions in `module`.
+/// Apply the [`cfg::Structurizer`] algorithm to all function definitions in `module`.
pub fn structurize_func_cfgs(module: &mut Module) {
let cx = &module.cx();
diff --git a/src/passes/link.rs b/src/passes/link.rs
index 77e04aa..59f8c1e 100644
--- a/src/passes/link.rs
+++ b/src/passes/link.rs
@@ -11,13 +11,13 @@ use std::collections::VecDeque;
// roots and then only other exports if they're used by imports.
/// Remove exports which aren't "roots" (`is_root(export_key)` returns `false`),
-/// and which aren't otherwise kept alive by a "root" (through `Import::LinkName`
-/// declarations, with `name` matching `ExportKey::LinkName`), either directly
+/// and which aren't otherwise kept alive by a "root" (through [`Import::LinkName`]
+/// declarations, with `name` matching [`ExportKey::LinkName`]), either directly
/// or transitively (including through any number of imports).
///
/// In essence, other than the "root" exports, `minimize_exports` only keeps the
/// exports that `resolve_imports` would use, and is recommended to first call
-/// `minimize_exports` before using `resolve_imports`, to reduce work.
+/// `minimize_exports` before using [`resolve_imports`], to reduce work.
///
/// Note that the "dead" definitions are not removed from the module, and any
/// external references to them could still be used (e.g. from a clone of the
@@ -103,9 +103,9 @@ impl Visitor<'_> for LiveExportCollector<'_> {
}
}
-/// Remap `Import::LinkName` to definitions exported as `ExportKey::LinkName`.
+/// Remap [`Import::LinkName`] to definitions exported as [`ExportKey::LinkName`].
///
-/// To reduce the work performed, calling `minimize_exports` first is recommended.
+/// To reduce the work performed, calling [`minimize_exports`] first is recommended.
//
// FIXME(eddyb) make this operate on multiple modules.
pub fn resolve_imports(module: &mut Module) {
diff --git a/src/print/mod.rs b/src/print/mod.rs
index 85bd75f..5f4dee3 100644
--- a/src/print/mod.rs
+++ b/src/print/mod.rs
@@ -1,3 +1,19 @@
+//! Pretty-printing anything in the IR, from whole [`Module`]s to their leaves.
+//!
+//! # Usage
+//!
+//! To start, create a [`Plan`] (through e.g. [`Plan::for_root`] or [`Plan::for_module`]),
+//! which will track the entire (transitive) set of (interned/entity) dependencies
+//! required to produce complete pretty-printing outputs.
+//!
+//! On a [`Plan`], use [`.pretty_print()`](Plan::pretty_print) to print everything,
+//! and get a "pretty document", with layout (inline-vs-multi-line decisions,
+//! auto-indentation, etc.) already performed, and which supports outputting:
+//! * plain text: `fmt::Display` (`{}` formatting) or `.to_string()`
+//! * HTML (styled and hyperlinked): [`.render_to_html()`](Versions::render_to_html)
+#![allow(rustdoc::private_intra_doc_links)]
+//! (returning a [`pretty::HtmlSnippet`])
+
// FIXME(eddyb) stop using `itertools` for methods like `intersperse` when they
// get stabilized on `Iterator` instead.
#![allow(unstable_name_collisions)]
@@ -25,39 +41,40 @@ mod pretty;
///
/// In order to represent parts of a DAG textually, it first needs to have its
/// nodes "flattened" into an order (also known as "topo(logical) sorting"),
-/// which `Plan` wholly records, before any printing can commence.
+/// which [`Plan`] wholly records, before any printing can commence.
///
/// Additionally, nodes without a significant identity (i.e. interned ones) may
/// have their separate definition omitted in some cases where printing them
/// inline at their use site(s) is preferred (e.g. when they have a single use).
///
-/// Once a `Plan` contains everything that needs to be printed, formatting the
-/// `Plan` value with `fmt::Display` will print all of the nodes in the `Plan`.
+/// Once a [`Plan`] contains everything that needs to be printed, calling the
+/// [`.pretty_print()`](Plan::pretty_print) method will print all of the nodes
+/// in the [`Plan`], and its return value can be e.g. formatted with [`fmt::Display`].
pub struct Plan<'a> {
cx: &'a Context,
- /// When visiting module-stored nodes, the `Module` is needed to map the
- /// `Node` to the (per-version) definition, which is then stored in the
- /// (per-version) `FxHashMap` within `per_version_name_and_node_defs`.
+ /// When visiting module-stored nodes, the [`Module`] is needed to map the
+ /// [`Node`] to the (per-version) definition, which is then stored in the
+ /// (per-version) [`FxHashMap`] within `per_version_name_and_node_defs`.
current_module: Option<&'a Module>,
- /// Versions allow comparing multiple copies of the same e.g. `Module`,
- /// with definitions sharing a `Node` key being shown together.
+ /// Versions allow comparing multiple copies of the same e.g. [`Module`],
+ /// with definitions sharing a [`Node`] key being shown together.
///
/// Each `per_version_name_and_node_defs` entry contains a "version" with:
/// * a descriptive name (e.g. the name of a pass that produced that version)
/// * the name is left empty in the default single-version mode
- /// * its `Node` definitions (dynamic via the `DynNodeDef` helper trait)
+ /// * its [`Node`] definitions (dynamic via the [`DynNodeDef`] helper trait)
///
- /// Specific `Node`s may be present in only a subset of versions, and such
+ /// Specific [`Node`]s may be present in only a subset of versions, and such
/// a distinction will be reflected in the output.
///
- /// For `Node` collection, the last entry consistutes the "active" version.
+ /// For [`Node`] collection, the last entry consistutes the "active" version.
per_version_name_and_node_defs: Vec<(String, FxHashMap>)>,
- /// Merged per-`Use` counts across all versions.
+ /// Merged per-[`Use`] counts across all versions.
///
- /// That is, each `Use` maps to the largest count of that `Use` in any version,
+ /// That is, each [`Use`] maps to the largest count of that [`Use`] in any version,
/// as opposed to their sum. This approach avoids pessimizing e.g. inline
/// printing of interned definitions, which may need the use count to be `1`.
use_counts: FxIndexMap |