diff --git a/.github/workflows/rustdoc-pages.yml b/.github/workflows/rustdoc-pages.yml index cf38182..93dc0fc 100644 --- a/.github/workflows/rustdoc-pages.yml +++ b/.github/workflows/rustdoc-pages.yml @@ -29,7 +29,11 @@ jobs: toolchain: nightly override: true - name: Build Docs - run: RUSTDOCFLAGS="--cfg docs_build" cargo doc --document-private-items + run: | + RUSTDOCFLAGS="--cfg docs_build --cfg git_main_docs" \ + GIT_MAIN_COMMIT="$(git rev-parse HEAD)" \ + GIT_MAIN_DESCRIBE="$(git describe --tags --always)" \ + cargo doc --document-private-items - name: Setup Pages id: pages uses: actions/configure-pages@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1afea18..1fe1a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Conversions from/to SPIR-V (`spv::lower`/`spv::lift`). - Control-flow structurizer, from CFGs to SPIR-T's stricter structured control-flow. -- Pretty-printer with (syntax-highlighted and hyperlinked) HTML output. +- Pretty-printer with (styled and hyperlinked) HTML output. [Unreleased]: https://github.com/EmbarkStudios/spirt/compare/0.0.0...HEAD diff --git a/Cargo.toml b/Cargo.toml index 9ab5cbd..831d9ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,8 @@ rustc-hash = "1.1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" smallvec = { version = "1.7.0", features = ["serde", "union"] } + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--document-private-items"] +rustc-args = ["--cfg", "docsrs"] diff --git a/README.md b/README.md index b630488..93e956c 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ With the initial focus being on [Rust-GPU]'s usecase, various (otherwise desirab
-**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, @@ -70,14 +87,14 @@ pub struct ExpectedVsFound { pub found: F, } -/// Print `Plan` top-level entry, an effective reification of SPIR-T's implicit DAG. +/// Print [`Plan`] top-level entry, an effective reification of SPIR-T's implicit DAG. #[derive(Copy, Clone, PartialEq, Eq, Hash)] enum Node { - /// Either a whole `Module`, or some other printable type passed to - /// `Plan::for_root` (e.g. `ExpectedVsFound`). + /// Either a whole [`Module`], or some other printable type passed to + /// [`Plan::for_root`] (e.g. [`ExpectedVsFound`]). Root, - /// Definitions for all `CxInterned` that need them, grouped together. + /// Definitions for all [`CxInterned`] that need them, grouped together. AllCxInterned, // FIXME(eddyb) these do not support multiple `Module`s as they don't have @@ -107,11 +124,11 @@ impl Node { } } -/// Helper for `Node::AllCxInterned`'s definition, to be used in `node_defs`. +/// Helper for [`Node::AllCxInterned`]'s definition, to be used in `node_defs`. struct AllCxInterned; -/// Everything interned in `Context`, that might need to be printed once -/// (as part of `Node::AllCxInterned`) and referenced multiple times. +/// Anything interned in [`Context`], that might need to be printed once +/// (as part of [`Node::AllCxInterned`]) and referenced multiple times. #[derive(Copy, Clone, PartialEq, Eq, Hash)] enum CxInterned { AttrSet(AttrSet), @@ -129,7 +146,7 @@ impl CxInterned { } } -/// A `Print` `Output` type that splits the attributes from the main body of the +/// A [`Print`] `Output` type that splits the attributes from the main body of the /// definition, allowing additional processing before they get concatenated. #[derive(Default)] pub struct AttrsAndDef { @@ -139,7 +156,7 @@ pub struct AttrsAndDef { /// * ` = ...` for `name = ...` /// * `(...) {...}` for `name(...) {...}` (i.e. functions) /// - /// Where `name` is added later + /// Where `name` is added later (i.e. between `attrs` and `def_without_name`). pub def_without_name: pretty::Fragment, } @@ -200,7 +217,7 @@ impl Use { } impl<'a> Plan<'a> { - /// Create a `Plan` with all of `root`'s dependencies, followed by `root` itself. + /// Create a [`Plan`] with all of `root`'s dependencies, followed by `root` itself. // // FIXME(eddyb) consider renaming this and removing the `for_module` shorthand. pub fn for_root( @@ -217,21 +234,21 @@ impl<'a> Plan<'a> { plan } - /// Create a `Plan` with all of `module`'s contents. + /// Create a [`Plan`] with all of `module`'s contents. /// /// Shorthand for `Plan::for_root(module.cx_ref(), module)`. pub fn for_module(module: &'a Module) -> Self { Self::for_root(module.cx_ref(), module) } - /// Create a `Plan` that combines `Plan::for_root` from each version. + /// Create a [`Plan`] that combines [`Plan::for_root`] from each version. /// /// Each version has a string, which should contain a descriptive name /// (e.g. the name of a pass that produced that version). /// /// While the roots (and their dependencies) can be entirely unrelated, the /// output won't be very useful in that case. For ideal results, most of the - /// same entities (e.g. `GlobalVar` or `Func`) should be in most versions, + /// same entities (e.g. [`GlobalVar`] or [`Func`]) should be in most versions, /// with most of the changes being limited to within their definitions. pub fn for_versions( cx: &'a Context, @@ -431,8 +448,9 @@ impl Visit for AllCxInterned { fn visit_with<'a>(&'a self, _visitor: &mut impl Visitor<'a>) {} } +#[allow(rustdoc::private_intra_doc_links)] /// Wrapper for handling the difference between single-version and multi-version -/// output, which aren't expressible in `pretty::Fragment`. +/// output, which aren't expressible in [`pretty::Fragment`]. // // FIXME(eddyb) introduce a `pretty::Node` variant capable of handling this, // but that's complicated wrt non-HTML output, if they're to also be 2D tables. @@ -623,11 +641,12 @@ impl Versions { } impl Plan<'_> { - /// Print the whole `Plan` to a `Versions` and perform - /// layout on its `pretty::Fragment`s. + #[allow(rustdoc::private_intra_doc_links)] + /// Print the whole [`Plan`] to a [`Versions`] and perform + /// layout on its [`pretty::Fragment`]s. /// - /// The resulting `Versions` value supports - /// `fmt::Display` for convenience, but also more specific methods + /// The resulting [`Versions`] value supports + /// [`fmt::Display`] for convenience, but also more specific methods /// (e.g. HTML output). pub fn pretty_print(&self) -> Versions { // FIXME(eddyb) make max line width configurable. @@ -643,12 +662,12 @@ pub struct Printer<'a> { use_styles: FxIndexMap, } -/// How an `Use` of a definition should be printed. +/// How an [`Use`] of a definition should be printed. #[derive(Copy, Clone)] enum UseStyle { /// Refer to the definition by its category and an `idx` (e.g. `"type123"`). Anon { - /// For intra-function `Use`s (i.e. `Use::ControlRegionLabel` and values), + /// For intra-function [`Use`]s (i.e. [`Use::ControlRegionLabel`] and values), /// this disambiguates the parent function (for e.g. anchors). parent_func: Option, @@ -1017,7 +1036,7 @@ impl<'a> Printer<'a> { /// SPIR-T, and should not be printed (e.g. decorations' target IDs). /// But if `print_id` doesn't need to return `Option<_>` (for `None`), its /// return type can skip the `Option` entirely (which allows passing in the - /// `Print::print` method, instead of a closure, as `print_id`). + /// [`Print::print`] method, instead of a closure, as `print_id`). /// /// Immediate operands are wrapped in angle brackets, while `ID` operands are /// wrapped in parentheses, e.g.: `OpFoo(v1, v2)`. @@ -1105,7 +1124,7 @@ impl<'a> Printer<'a> { } impl AttrsAndDef { - /// Concat `attrs`, `name` and `def_without_name` into a `pretty::Fragment`, + /// Concat `attrs`, `name` and `def_without_name` into a [`pretty::Fragment`], /// effectively "filling in" the `name` missing from `def_without_name`. /// /// If `name` starts with an anchor definition, the definition of that anchor @@ -1168,7 +1187,7 @@ impl, F: Print> P } impl Use { - /// Common implementation for `Use::print` and `Use::print_as_def`. + /// Common implementation for [`Use::print`] and [`Use::print_as_def`]. fn print_as_ref_or_def(&self, printer: &Printer<'_>, is_def: bool) -> pretty::Fragment { let style = printer .use_styles diff --git a/src/print/pretty.rs b/src/print/pretty.rs index eecba8f..f59cd42 100644 --- a/src/print/pretty.rs +++ b/src/print/pretty.rs @@ -6,7 +6,9 @@ use std::borrow::Cow; use std::fmt::Write as _; use std::{fmt, iter, mem}; -/// Part of a pretty document, made up of `Node`s. +/// Part of a pretty document, made up of [`Node`]s. +// +// FIXME(eddyb) `Document` might be too long, what about renaming this to `Doc`? #[derive(Clone, Default, PartialEq)] pub struct Fragment { pub nodes: SmallVec<[Node; 8]>, @@ -19,10 +21,10 @@ pub enum Node { // FIXME(eddyb) should this contain a `Node` instead of being text-only? StyledText(Box<(Styles, Cow<'static, str>)>), - /// Container for `Fragment`s, using block layout (indented on separate lines). + /// Container for [`Fragment`]s, using block layout (indented on separate lines). IndentedBlock(Vec), - /// Container for `Fragment`s, either using inline layout (all on one line) + /// Container for [`Fragment`]s, either using inline layout (all on one line) /// or block layout (indented on separate lines). InlineOrIndentedBlock(Vec), @@ -127,7 +129,7 @@ impl Fragment { } } - /// Perform layout on the `Fragment`, limiting lines to `max_line_width` + /// Perform layout on the [`Fragment`], limiting lines to `max_line_width` /// columns where possible. pub fn layout_with_max_line_width(mut self, max_line_width: usize) -> FragmentPostLayout { self.approx_layout(MaxWidths { @@ -232,7 +234,7 @@ impl HtmlSnippet { } impl FragmentPostLayout { - /// Flatten the `Fragment` to HTML, producing a `HtmlSnippet`. + /// Flatten the [`Fragment`] to HTML, producing a [`HtmlSnippet`]. // // FIXME(eddyb) provide a non-allocating version. pub fn render_to_html(&self) -> HtmlSnippet { @@ -361,12 +363,12 @@ impl FragmentPostLayout { // Rendering implementation details (including approximate layout). -/// The approximate shape of a `Node`, regarding its 2D placement. +/// The approximate shape of a [`Node`], regarding its 2D placement. #[derive(Copy, Clone)] enum ApproxLayout { /// Only occupies part of a line, (at most) `worst_width` columns wide. /// - /// `worst_width` can exceed the `inline` field of `MaxWidths`, in which + /// `worst_width` can exceed the `inline` field of [`MaxWidths`], in which /// case the choice of inline vs block is instead made by a surrounding node. Inline { worst_width: usize }, @@ -423,7 +425,7 @@ impl ApproxLayout { } } -/// Maximum numbers of columns, available to a `Node`, for both inline layout +/// Maximum numbers of columns, available to a [`Node`], for both inline layout /// and block layout (i.e. multi-line with indentation). /// /// That is, these are the best-case scenarios across all possible choices of @@ -439,10 +441,10 @@ struct MaxWidths { const INDENT: &str = " "; impl Node { - /// Determine the "rigid" component of the `ApproxLayout` of this `Node`. + /// Determine the "rigid" component of the [`ApproxLayout`] of this [`Node`]. /// - /// That is, this accounts for the parts of the `Node` that don't depend on - /// contextual sizing, i.e. `MaxWidths` (see also `approx_flex_layout`). + /// That is, this accounts for the parts of the [`Node`] that don't depend on + /// contextual sizing, i.e. [`MaxWidths`] (see also `approx_flex_layout`). fn approx_rigid_layout(&self) -> ApproxLayout { // HACK(eddyb) workaround for the `Self::StyledText` arm not being able // to destructure through the `Box<(_, Cow)>`. @@ -499,11 +501,11 @@ impl Node { } } - /// Determine the "flexible" component of the `ApproxLayout` of this `Node`, + /// Determine the "flexible" component of the [`ApproxLayout`] of this [`Node`], /// potentially making adjustments in order to fit within `max_widths`. /// - /// That is, this accounts for the parts of the `Node` that do depend on - /// contextual sizing, i.e. `MaxWidths` (see also `approx_rigid_layout`). + /// That is, this accounts for the parts of the [`Node`] that do depend on + /// contextual sizing, i.e. [`MaxWidths`] (see also `approx_rigid_layout`). fn approx_flex_layout(&mut self, max_widths: MaxWidths) -> ApproxLayout { match self { Self::IndentedBlock(fragments) => { @@ -584,7 +586,7 @@ impl Node { } impl Fragment { - /// Determine the `ApproxLayout` of this `Fragment`, potentially making + /// Determine the [`ApproxLayout`] of this [`Fragment`], potentially making /// adjustments in order to fit within `max_widths`. fn approx_layout(&mut self, max_widths: MaxWidths) -> ApproxLayout { let mut layout = ApproxLayout::Inline { worst_width: 0 }; @@ -648,8 +650,8 @@ impl Fragment { /// Line-oriented operation (i.e. as if lines are stored separately). /// /// However, a representation that stores lines separately doesn't really exist, -/// and instead `LineOp`s are (statefully) transformed into `TextOp`s on the fly -/// (see `LineOp::interpret_with`). +/// and instead [`LineOp`]s are (statefully) transformed into [`TextOp`]s on the fly +/// (see [`LineOp::interpret_with`]). #[derive(Copy, Clone)] enum LineOp<'a> { PushIndent, @@ -669,7 +671,7 @@ enum Break { } impl Node { - /// Flatten the `Fragment` to `LineOp`s, passed to `each_line_op`. + /// Flatten the [`Node`] to [`LineOp`]s, passed to `each_line_op`. fn render_to_line_ops<'a>( &'a self, each_line_op: &mut impl FnMut(LineOp<'a>), @@ -727,7 +729,7 @@ impl Node { } impl Fragment { - /// Flatten the `Fragment` to `LineOp`s, passed to `each_line_op`. + /// Flatten the [`Fragment`] to [`LineOp`]s, passed to `each_line_op`. fn render_to_line_ops<'a>( &'a self, each_line_op: &mut impl FnMut(LineOp<'a>), @@ -748,8 +750,8 @@ enum TextOp<'a> { } impl<'a> LineOp<'a> { - /// Expand `LineOp`s passed to the returned `impl FnMut(LineOp<'a>)` closure, - /// forwarding the expanded `TextOp`s to `each_text_op`. + /// Expand [`LineOp`]s passed to the returned `impl FnMut(LineOp<'a>)` closure, + /// forwarding the expanded [`TextOp`]s to `each_text_op`. // // FIXME(eddyb) this'd be nicer if instead of returning a closure, it could // be passed to an `impl for)> FnOnce(F)` callback. @@ -838,7 +840,7 @@ impl<'a> LineOp<'a> { // // FIXME(eddyb) should these be methods on `Node`/`Fragment`? -/// Constructs the `Fragment` corresponding to one of: +/// Constructs the [`Fragment`] corresponding to one of: /// * inline layout: `header + " " + contents.join(" ")` /// * block layout: `header + "\n" + indent(contents).join("\n")` pub fn join_space( @@ -858,7 +860,7 @@ pub fn join_space( ]) } -/// Constructs the `Fragment` corresponding to one of: +/// Constructs the [`Fragment`] corresponding to one of: /// * inline layout: `prefix + contents.join(", ") + suffix` /// * block layout: `prefix + "\n" + indent(contents).join(",\n") + ",\n" + suffix` pub fn join_comma_sep( diff --git a/src/spv/lift.rs b/src/spv/lift.rs index b44397d..e9af584 100644 --- a/src/spv/lift.rs +++ b/src/spv/lift.rs @@ -246,11 +246,11 @@ struct FuncLifting<'a> { blocks: FxIndexMap>, } -/// What determines the values for `Value::ControlRegionInput`s, for a specific +/// What determines the values for [`Value::ControlRegionInput`]s, for a specific /// region (effectively the subset of "region parents" that support inputs). /// -/// Note that this is not used when a `cfg::ControlInst` has `target_inputs`, -/// and the target `ControlRegion` itself has phis for its `inputs`. +/// Note that this is not used when a [`cfg::ControlInst`] has `target_inputs`, +/// and the target [`ControlRegion`] itself has phis for its `inputs`. enum RegionInputsSource { FuncParams, LoopHeaderPhis(ControlNode), @@ -286,13 +286,14 @@ struct Phi { default_value: Option, } -/// Similar to `cfg::ControlInst`, except: -/// * `targets` use `CfgPoint`s instead of `ControlRegion`s, to be able to +/// Similar to [`cfg::ControlInst`], except: +/// * `targets` use [`CfgPoint`]s instead of [`ControlRegion`]s, to be able to /// reach any of the SPIR-V blocks being created during lifting /// * φ ("phi") values can be provided for targets regardless of "which side" of /// the structured control-flow they are for ("region input" vs "node output") /// * optional `merge` (for `OpSelectionMerge`/`OpLoopMerge`) -/// * existing data is borrowed (from the `FuncDefBody`) wherever possible +/// * existing data is borrowed (from the [`FuncDefBody`](crate::FuncDefBody)), +/// wherever possible struct Terminator<'a> { attrs: AttrSet, @@ -370,8 +371,8 @@ impl<'a> NeedsIdsCollector<'a> { } } -/// Helper type for deep traversal of the CFG (as a graph of `CfgPoint`s), which -/// tracks the necessary context for navigating a `ControlRegion`/`ControlNode`. +/// Helper type for deep traversal of the CFG (as a graph of [`CfgPoint`]s), which +/// tracks the necessary context for navigating a [`ControlRegion`]/[`ControlNode`]. #[derive(Copy, Clone)] struct CfgCursor<'p, P = CfgPoint> { point: P, @@ -384,7 +385,7 @@ enum ControlParent { } impl<'a, 'p> FuncAt<'a, CfgCursor<'p>> { - /// Return the next `CfgPoint` (wrapped in `CfgCursor`) in a linear + /// Return the next [`CfgPoint`] (wrapped in [`CfgCursor`]) in a linear /// chain within structured control-flow (i.e. no branching to child regions). fn unique_successor(self) -> Option> { let cursor = self.position; @@ -445,9 +446,9 @@ impl<'a, 'p> FuncAt<'a, CfgCursor<'p>> { } impl<'a> FuncAt<'a, ControlRegion> { - /// Traverse every `CfgPoint` (deeply) contained in this `ControlRegion`, - /// in reverse post-order (RPO), with `f` receiving each `CfgPoint` - /// in turn (wrapped in `CfgCursor`, for further traversal flexibility), + /// Traverse every [`CfgPoint`] (deeply) contained in this [`ControlRegion`], + /// in reverse post-order (RPO), with `f` receiving each [`CfgPoint`] + /// in turn (wrapped in [`CfgCursor`], for further traversal flexibility), /// and being able to stop iteration by returning `Err`. /// /// RPO iteration over a CFG provides certain guarantees, most importantly diff --git a/src/spv/lower.rs b/src/spv/lower.rs index be48801..26a9d78 100644 --- a/src/spv/lower.rs +++ b/src/spv/lower.rs @@ -59,7 +59,7 @@ enum Export { }, } -/// Deferred `FuncDefBody`, needed because some IDs are initially forward refs. +/// Deferred [`FuncDefBody`], needed because some IDs are initially forward refs. struct FuncBody { func_id: spv::Id, func: Func, diff --git a/src/spv/mod.rs b/src/spv/mod.rs index 76859f2..2067e22 100644 --- a/src/spv/mod.rs +++ b/src/spv/mod.rs @@ -1,10 +1,7 @@ -use crate::{FxIndexMap, InternedStr}; -use smallvec::SmallVec; -use std::collections::{BTreeMap, BTreeSet}; -use std::iter; -use std::num::NonZeroU32; -use std::string::FromUtf8Error; +//! SPIR-V support, mainly conversions to/from SPIR-T ([`lower`]/[`lift`]). +// NOTE(eddyb) all the modules are declared here, but they're documented "inside" +// (i.e. using inner doc comments). pub mod lift; pub mod lower; pub mod print; @@ -12,6 +9,14 @@ pub mod read; pub mod spec; pub mod write; +use crate::{FxIndexMap, InternedStr}; +use smallvec::SmallVec; +use std::collections::{BTreeMap, BTreeSet}; +use std::iter; +use std::num::NonZeroU32; +use std::string::FromUtf8Error; + +/// Semantic properties of a SPIR-V module (not tied to any IDs). #[derive(Clone)] pub struct Dialect { pub version_major: u8, @@ -24,6 +29,7 @@ pub struct Dialect { pub memory_model: u32, } +/// Non-semantic details (i.e. debuginfo) of a SPIR-V module (not tied to any IDs). #[derive(Clone)] pub struct ModuleDebugInfo { pub original_generator_magic: Option, @@ -64,7 +70,7 @@ impl From for Inst { } } -/// A full SPIR-V instruction (like `Inst` but including input/output ID operands). +/// A full SPIR-V instruction (like [`Inst`], but including input/output ID operands). pub struct InstWithIds { pub without_ids: Inst, @@ -89,6 +95,8 @@ impl std::ops::DerefMut for InstWithIds { } } +/// SPIR-V immediate (one word, longer immediates are a sequence of multiple [`Imm`]s). +// // FIXME(eddyb) consider replacing with a `struct` e.g.: // `{ first: bool, last: bool, kind: OperandKind, word: u32 }` #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -98,13 +106,15 @@ pub enum Imm { LongCont(spec::OperandKind, u32), } +/// SPIR-V ID. pub type Id = NonZeroU32; // FIXME(eddyb) pick a "small string" crate, and fine-tune its inline size, // instead of allocating a whole `String`. -/// Given a single `LiteralString` (as one `Imm::Short` or a `Imm::LongStart` -/// followed by some number of `Imm::LongCont` - will panic otherwise), returns a -/// Rust `String` if the literal is valid UTF-8, or the validation error otherwise. +// +/// Given a single `LiteralString` (as one [`Imm::Short`] or a [`Imm::LongStart`] +/// followed by some number of [`Imm::LongCont`] - will panic otherwise), returns a +/// Rust [`String`] if the literal is valid UTF-8, or the validation error otherwise. fn extract_literal_string(imms: &[Imm]) -> Result { let wk = &spec::Spec::get().well_known; diff --git a/src/spv/print.rs b/src/spv/print.rs index 0909150..4c7076b 100644 --- a/src/spv/print.rs +++ b/src/spv/print.rs @@ -16,7 +16,7 @@ use std::{iter, mem, str}; pub enum Token { /// An inconsistency was detected in the operands to be printed. /// For stylistic consistency, the error message is always found wrapped in - /// a block comment (i.e. the `String` is always of the form `"/* ... */"`). + /// a block comment (i.e. the [`String`] is always of the form `"/* ... */"`). Error(String), // FIXME(eddyb) perhaps encode the hierarchical structure of e.g. enumerand @@ -33,7 +33,7 @@ pub enum Token { Id(ID), } -/// All the `Token`s outputted by printing one single ("logical") SPIR-V operand, +/// All the [`Token`]s outputted by printing one single ("logical") SPIR-V operand, /// which may be concatenated (after separately processing `ID`s) to obtain a /// complete plain-text version of the printed operand. pub struct TokensForOperand { @@ -282,7 +282,7 @@ pub fn operand_from_imms(imms: impl IntoIterator) -> TokensForO } /// Group (ordered according to `opcode`) `imms` and `ids` into logical operands -/// (i.e. long immediates are unflattened) and produce one `TokensForOperand` by +/// (i.e. long immediates are unflattened) and produce one [`TokensForOperand`] by /// printing each of them. pub fn inst_operands( opcode: spec::Opcode, diff --git a/src/spv/spec.rs b/src/spv/spec.rs index 58fe46d..4870962 100644 --- a/src/spv/spec.rs +++ b/src/spv/spec.rs @@ -1,3 +1,5 @@ +//! SPIR-V specification parsing/indexing. + use arrayvec::ArrayVec; use lazy_static::lazy_static; use rustc_hash::FxHashMap; @@ -151,7 +153,7 @@ impl indexed::FlatIdx for Opcode { } impl Opcode { - /// Lookup the name & definition for `opcode` in the lazily-loaded `Spec`, + /// Lookup the name & definition for `opcode` in the lazily-loaded [`Spec`], /// returning `None` if it's not a known opcode. pub fn try_from_u16_with_name_and_def( opcode: u16, @@ -165,19 +167,19 @@ impl Opcode { self.0 } - /// Lookup the name & definition for this opcode in the lazily-loaded `Spec`. + /// Lookup the name & definition for this opcode in the lazily-loaded [`Spec`]. #[inline] pub fn name_and_def(self) -> (&'static str, &'static InstructionDef) { Spec::get().instructions.get_named(self).unwrap() } - /// Lookup the name for this opcode in the lazily-loaded `Spec`. + /// Lookup the name for this opcode in the lazily-loaded [`Spec`]. #[inline] pub fn name(self) -> &'static str { self.name_and_def().0 } - /// Lookup the definition for this opcode in the lazily-loaded `Spec`. + /// Lookup the definition for this opcode in the lazily-loaded [`Spec`]. #[inline] pub fn def(self) -> &'static InstructionDef { self.name_and_def().1 @@ -220,8 +222,8 @@ pub enum OperandMode { } impl InstructionDef { - /// Return a (potentially infinite) iterator of `OperandKind`s, along with - /// the `OperandMode` indicating whether an operand is expected (`Required`), + /// Return a (potentially infinite) iterator of [`OperandKind`]s, along with + /// the [`OperandMode`] indicating whether an operand is expected (`Required`), /// or that an operand's absence signals the end of operands (`Optional`), /// which is also the exit signal for the "rest operands" infinite iterators. pub fn all_operands(&self) -> impl Iterator + '_ { @@ -262,19 +264,19 @@ impl indexed::FlatIdx for OperandKind { } impl OperandKind { - /// Lookup the name & definition for this operand kind in the lazily-loaded `Spec`. + /// Lookup the name & definition for this operand kind in the lazily-loaded [`Spec`]. #[inline] pub fn name_and_def(self) -> (&'static str, &'static OperandKindDef) { Spec::get().operand_kinds.get_named(self).unwrap() } - /// Lookup the name for this operand kind in the lazily-loaded `Spec`. + /// Lookup the name for this operand kind in the lazily-loaded [`Spec`]. #[inline] pub fn name(self) -> &'static str { self.name_and_def().0 } - /// Lookup the definition for this operand kind in the lazily-loaded `Spec`. + /// Lookup the definition for this operand kind in the lazily-loaded [`Spec`]. #[inline] pub fn def(self) -> &'static OperandKindDef { self.name_and_def().1 @@ -310,7 +312,7 @@ impl BitIdx { } } - /// Returns an iterator of `BitIdx`s, from which `x` can be reconstructed + /// Returns an iterator of [`BitIdx`]s, from which `x` can be reconstructed /// by OR-ing together `1 << i` for every `BitIdx(i)`. /// /// The iterator is ordered: lower bit indices appear before higher ones. @@ -347,8 +349,8 @@ pub struct Enumerant { } impl Enumerant { - /// Return a (potentially infinite) iterator of `OperandKind`s, along with - /// the `OperandMode` indicating whether an operand is expected (`Required`), + /// Return a (potentially infinite) iterator of [`OperandKind`]s, along with + /// the [`OperandMode`] indicating whether an operand is expected (`Required`), /// or that an operand's absence signals the end of operands (`Optional`), /// which is also the exit signal for the "rest operands" infinite iterators. pub fn all_params(&self) -> impl Iterator + '_ { @@ -377,7 +379,7 @@ pub enum LiteralSize { } impl Spec { - /// Return a lazily-loaded `Spec` (only does significant work for the first call). + /// Return a lazily-loaded [`Spec`] (only does significant work for the first call). #[inline(always)] #[must_use] pub fn get() -> &'static Spec { @@ -402,7 +404,7 @@ impl Spec { &SPEC } - /// Implementation detail of `Spec::get`, indexes the raw data to produce a `Spec`. + /// Implementation detail of [`Spec::get`], indexes the raw data to produce a [`Spec`]. fn from_raw(raw_core_grammar: raw::CoreGrammar<'static>) -> Self { /// Helper for picking a name when the same index has multiple names. fn preferred_name_between_dups<'a>(a: &'a str, b: &'a str) -> &'a str { @@ -1003,7 +1005,7 @@ pub mod indexed { } } - /// Flat array (`Vec`) storage, likely used with compact indices. + /// Flat array ([`Vec`]) storage, likely used with compact indices. pub enum Flat {} impl StorageShape for Flat { @@ -1013,7 +1015,7 @@ pub mod indexed { } } - /// Like `Flat`, but the `Vec` elements are wrapped in `Option`. + /// Like [`Flat`], but the [`Vec`] elements are wrapped in [`Option`]. pub enum FlatWithHoles {} impl StorageShape for FlatWithHoles { @@ -1031,7 +1033,7 @@ pub mod indexed { /// than the standard range, the blockiness allows some optimizations pub enum KhrSegmented {} - /// Khronos-oriented segmented sparse array (see `KhrSegmented`). + /// Khronos-oriented segmented sparse array (see [`KhrSegmented`]). pub struct KhrSegmentedVec { /// Concatenation of values for indices lower than `4096`, with values /// for indices in a `64`-sized/aligned block starting at/above `4096`. @@ -1124,7 +1126,7 @@ pub mod indexed { } } - /// Construct a `KhrSegmentedVec` out of an iterator with ordered indices. + /// Construct a [`KhrSegmentedVec`] out of an iterator with ordered indices. /// /// An exception is made for duplicates, which have to be handled by the /// `merge_duplicates` closure, instead of being outright disallowed. diff --git a/src/transform.rs b/src/transform.rs index a693a47..f7313e3 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -1,3 +1,5 @@ +//! Mutable IR traversal. + use crate::func_at::FuncAtMut; use crate::{ cfg, spv, AddrSpace, Attr, AttrSet, AttrSetDef, Const, ConstCtor, ConstDef, ControlNode, @@ -42,8 +44,8 @@ impl Transformed { // can call e.g. `Transformed::foo(...)` methods. impl Transformed<()> { /// Map every element of an iterator through `transform_elem` and return - /// `Transformed::Changed(new_iter)` if any `transform_elem` call returned - /// `Transformed::Changed`, with `new_iter` containing a combination of the + /// [`Transformed::Changed(new_iter)`] if any `transform_elem` call returned + /// [`Transformed::Changed`], with `new_iter` containing a combination of the /// changed elements, and clones of the unchanged elements. pub fn map_iter<'a, T: 'a + Clone>( iter: impl Iterator + Clone + 'a, @@ -76,9 +78,9 @@ impl Transformed<()> { } } -/// Helper macro to create a combined `Transformed` out of several variables, -/// each with their own transformation, where any `Transformed::Changed` input -/// will result in a `Transformed::Changed` output, using a combination of the +/// Helper macro to create a combined [`Transformed`] out of several variables, +/// each with their own transformation, where any [`Transformed::Changed`] input +/// will result in a [`Transformed::Changed`] output, using a combination of the /// changed inputs, and clones of the unchanged inputs. macro_rules! transform { // User-facing entry-point, dispatches to the internal more-explicit form. @@ -201,7 +203,7 @@ pub trait Transformer: Sized { /// Trait implemented on "transformable" types, to further "elaborate" a type by /// transforming its "interior" (i.e. variants and/or fields). /// -/// That is, an `impl InnerTransform for X` will call the relevant `Transformer` +/// That is, an `impl InnerTransform for X` will call the relevant [`Transformer`] /// method for each `X` field, effectively performing a single level of a deep /// transform. /// Also, if `Transformer::transform_X` exists for a given `X`, its default should @@ -213,7 +215,7 @@ pub trait InnerTransform: Sized { fn inner_transform_with(&self, transformer: &mut impl Transformer) -> Transformed; } -/// Like `InnerTransform`, but only for the `in_place_transform_X` cases. +/// Like [`InnerTransform`], but only for the `in_place_transform_X` cases. pub trait InnerInPlaceTransform { fn inner_in_place_transform_with(&mut self, transformer: &mut impl Transformer); } diff --git a/src/visit.rs b/src/visit.rs index 49c0893..d2f4653 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -1,3 +1,5 @@ +//! Immutable IR traversal. + use crate::func_at::FuncAt; use crate::{ cfg, spv, AddrSpace, Attr, AttrSet, AttrSetDef, Const, ConstCtor, ConstDef, ControlNode, @@ -69,7 +71,7 @@ pub trait Visitor<'a>: Sized { /// Trait implemented on "visitable" types (shallowly visitable, at least). /// -/// That is, an `impl Visit for X` will call the relevant `Visitor` method for +/// That is, an `impl Visit for X` will call the relevant [`Visitor`] method for /// `X`, typically named `Visitor::visit_X` or `Visitor::visit_X_use`. // // FIXME(eddyb) use this more (e.g. in implementing `InnerVisit`). @@ -121,7 +123,7 @@ impl_visit! { } } -/// Dynamic dispatch version of `Visit`. +/// Dynamic dispatch version of [`Visit`]. /// /// `dyn DynVisit<'a, V>` is possible, unlike `dyn Visit`, because of the /// `trait`-level type parameter `V`, which replaces the method parameter. @@ -138,7 +140,7 @@ impl<'a, T: Visit, V: Visitor<'a>> DynVisit<'a, V> for T { /// Trait implemented on "deeply visitable" types, to further "explore" a type /// by visiting its "interior" (i.e. variants and/or fields). /// -/// That is, an `impl InnerVisit for X` will call the relevant `Visitor` method +/// That is, an `impl InnerVisit for X` will call the relevant [`Visitor`] method /// for each `X` field, effectively performing a single level of a deep visit. /// Also, if `Visitor::visit_X` exists for a given `X`, its default should be to /// call `X::inner_visit_with` (i.e. so that visiting is mostly-deep by default). @@ -147,7 +149,7 @@ pub trait InnerVisit { fn inner_visit_with<'a>(&'a self, visitor: &mut impl Visitor<'a>); } -/// Dynamic dispatch version of `InnerVisit`. +/// Dynamic dispatch version of [`InnerVisit`]. /// /// `dyn DynInnerVisit<'a, V>` is possible, unlike `dyn InnerVisit`, because of /// the `trait`-level type parameter `V`, which replaces the method parameter.