spirt/print/
mod.rs

1//! Pretty-printing anything in the IR, from whole [`Module`]s to their leaves.
2//!
3//! # Usage
4//!
5//! To start, create a [`Plan`] (through e.g. [`Plan::for_root`] or [`Plan::for_module`]),
6//! which will track the entire (transitive) set of (interned/entity) dependencies
7//! required to produce complete pretty-printing outputs.
8//!
9//! On a [`Plan`], use [`.pretty_print()`](Plan::pretty_print) to print everything,
10//! and get a "pretty document", with layout (inline-vs-multi-line decisions,
11//! auto-indentation, etc.) already performed, and which supports outputting:
12//! * plain text: `fmt::Display` (`{}` formatting) or `.to_string()`
13//! * HTML (styled and hyperlinked): [`.render_to_html()`](Versions::render_to_html)
14#![allow(rustdoc::private_intra_doc_links)]
15//!   (returning a [`pretty::HtmlSnippet`])
16
17// FIXME(eddyb) stop using `itertools` for methods like `intersperse` when they
18// get stabilized on `Iterator` instead.
19#![allow(unstable_name_collisions)]
20use itertools::Itertools as _;
21
22use crate::func_at::FuncAt;
23use crate::print::multiversion::Versions;
24use crate::qptr::{self, QPtrAttr, QPtrMemUsage, QPtrMemUsageKind, QPtrOp, QPtrUsage};
25use crate::visit::{InnerVisit, Visit, Visitor};
26use crate::{
27    AddrSpace, Attr, AttrSet, AttrSetDef, Const, ConstDef, ConstKind, Context, ControlNode,
28    ControlNodeDef, ControlNodeKind, ControlNodeOutputDecl, ControlRegion, ControlRegionDef,
29    ControlRegionInputDecl, DataInst, DataInstDef, DataInstForm, DataInstFormDef, DataInstKind,
30    DeclDef, Diag, DiagLevel, DiagMsgPart, EntityListIter, ExportKey, Exportee, Func, FuncDecl,
31    FuncParam, FxIndexMap, FxIndexSet, GlobalVar, GlobalVarDecl, GlobalVarDefBody, Import, Module,
32    ModuleDebugInfo, ModuleDialect, OrdAssertEq, SelectionKind, Type, TypeDef, TypeKind,
33    TypeOrConst, Value, cfg, spv,
34};
35use arrayvec::ArrayVec;
36use itertools::Either;
37use rustc_hash::FxHashMap;
38use smallvec::SmallVec;
39use std::borrow::Cow;
40use std::collections::hash_map::Entry;
41use std::fmt::{self, Write as _};
42use std::hash::Hash;
43use std::mem;
44
45mod multiversion;
46mod pretty;
47
48/// "Definitions-before-uses" / "topo-sorted" printing plan.
49///
50/// In order to represent parts of a DAG textually, it first needs to have its
51/// nodes "flattened" into an order (also known as "topo(logical) sorting"),
52/// which [`Plan`] wholly records, before any printing can commence.
53///
54/// Additionally, nodes without a significant identity (i.e. interned ones) may
55/// have their separate definition omitted in some cases where printing them
56/// inline at their use site(s) is preferred (e.g. when they have a single use).
57///
58/// Once a [`Plan`] contains everything that needs to be printed, calling the
59/// [`.pretty_print()`](Plan::pretty_print) method will print all of the nodes
60/// in the [`Plan`], and its return value can be e.g. formatted with [`fmt::Display`].
61pub struct Plan<'a> {
62    cx: &'a Context,
63
64    /// When visiting module-stored nodes, the [`Module`] is needed to map the
65    /// [`Node`] to the (per-version) definition, which is then stored in the
66    /// (per-version) `node_defs` map.
67    current_module: Option<&'a Module>,
68
69    /// Versions allow comparing multiple copies of the same e.g. [`Module`],
70    /// with definitions sharing a [`Node`] key being shown together.
71    ///
72    /// Specific [`Node`]s may be present in only a subset of versions, and such
73    /// a distinction will be reflected in the output.
74    ///
75    /// For [`Node`] collection, `versions.last()` constitutes the "active" one.
76    versions: Vec<PlanVersion<'a>>,
77
78    /// Merged per-[`Use`] counts across all versions.
79    ///
80    /// That is, each [`Use`] maps to the largest count of that [`Use`] in any version,
81    /// as opposed to their sum. This approach avoids pessimizing e.g. inline
82    /// printing of interned definitions, which may need the use count to be `1`.
83    use_counts: FxIndexMap<Use, usize>,
84
85    /// Merged per-[`AttrSet`] unique SPIR-V `OpName`s across all versions.
86    ///
87    /// That is, each [`AttrSet`] maps to one of the SPIR-V `OpName`s contained
88    /// within the [`AttrSet`], as long as these three conditions are met:
89    /// * in each version using an [`AttrSet`], there is only one use of it
90    ///   (e.g. different [`Type`]s/[`Const`]s/etc. can't use the same [`AttrSet`])
91    /// * in each version using an [`AttrSet`], no other [`AttrSet`]s are used
92    ///   that "claim" the same SPIR-V `OpName`
93    /// * an [`AttrSet`] "claims" the same SPIR-V `OpName` across all versions
94    ///   using it (with per-version "claiming", only merged after the fact)
95    ///
96    /// Note that these conditions still allow the same SPIR-V `OpName` being
97    /// "claimed" by different [`AttrSet`]s, *as long as* they only show up in
98    /// *disjoint* versions (e.g. [`GlobalVarDecl`] attributes being modified
99    /// between versions, but keeping the same `OpName` attribute unchanged).
100    attrs_to_unique_spv_name: FxHashMap<AttrSet, Result<&'a spv::Inst, AmbiguousName>>,
101
102    /// Reverse map of `attrs_to_unique_spv_name_imms`, only used during visiting
103    /// (i.e. intra-version), to track which SPIR-V `OpName`s have been "claimed"
104    /// by some [`AttrSet`] and detect conflicts (which are resolved by marking
105    /// *both* overlapping [`AttrSet`], *and* the `OpName` itself, as ambiguous).
106    claimed_spv_names: FxHashMap<&'a spv::Inst, Result<AttrSet, AmbiguousName>>,
107}
108
109/// One version of a multi-version [`Plan`] (see also its `versions` field),
110/// or the sole one (in the single-version mode).
111struct PlanVersion<'a> {
112    /// Descriptive name for this version (e.g. the name of a pass that produced it),
113    /// or left empty (in the single-version mode).
114    name: String,
115
116    /// Definitions for all the [`Node`]s which may need to be printed later
117    /// (with the exception of [`Node::AllCxInterned`], which is special-cased).
118    node_defs: FxHashMap<Node, NodeDef<'a>>,
119
120    /// Either a whole [`Module`], or some other printable type passed to
121    /// [`Plan::for_root`]/[`Plan::for_versions`], which gets printed last,
122    /// after all of its dependencies (making up the rest of the [`Plan`]).
123    root: &'a dyn Print<Output = pretty::Fragment>,
124}
125
126/// Error type used when tracking `OpName` uniqueness.
127#[derive(Copy, Clone)]
128struct AmbiguousName;
129
130/// Print [`Plan`] top-level entry, an effective reification of SPIR-T's implicit DAG.
131#[derive(Copy, Clone, PartialEq, Eq, Hash)]
132enum Node {
133    /// Definitions for all [`CxInterned`] that need them, grouped together.
134    AllCxInterned,
135
136    // FIXME(eddyb) these do not support multiple `Module`s as they don't have
137    // any way to distinguish between instances of them from different `Module`s.
138    ModuleDialect,
139    ModuleDebugInfo,
140
141    GlobalVar(GlobalVar),
142    Func(Func),
143}
144
145impl Node {
146    fn keyword_and_name_prefix(self) -> Result<(&'static str, &'static str), &'static str> {
147        match self {
148            Self::AllCxInterned => Err("Node::AllCxInterned"),
149
150            // FIXME(eddyb) these don't have the same kind of `{name_prefix}{idx}`
151            // formatting, so maybe they don't belong in here to begin with?
152            Self::ModuleDialect => Ok(("module.dialect", "")),
153            Self::ModuleDebugInfo => Ok(("module.debug_info", "")),
154
155            Self::GlobalVar(_) => Ok(("global_var", "GV")),
156            Self::Func(_) => Ok(("func", "F")),
157        }
158    }
159}
160
161/// Definition of a [`Node`] (i.e. a reference pointing into a [`Module`]).
162///
163/// Note: [`Node::AllCxInterned`] does *not* have its own `NodeDef` variant,
164/// as it *must* be specially handled instead.
165#[derive(Copy, Clone, derive_more::From)]
166enum NodeDef<'a> {
167    ModuleDialect(&'a ModuleDialect),
168    ModuleDebugInfo(&'a ModuleDebugInfo),
169    GlobalVar(&'a GlobalVarDecl),
170    Func(&'a FuncDecl),
171}
172
173/// Anything interned in [`Context`], that might need to be printed once
174/// (as part of [`Node::AllCxInterned`]) and referenced multiple times.
175#[derive(Copy, Clone, PartialEq, Eq, Hash)]
176enum CxInterned {
177    Type(Type),
178    Const(Const),
179}
180
181impl CxInterned {
182    fn keyword_and_name_prefix(self) -> (&'static str, &'static str) {
183        match self {
184            Self::Type(_) => ("type", "T"),
185            Self::Const(_) => ("const", "C"),
186        }
187    }
188}
189
190#[derive(Copy, Clone, PartialEq, Eq, Hash)]
191enum Use {
192    Node(Node),
193
194    CxInterned(CxInterned),
195
196    ControlRegionLabel(ControlRegion),
197
198    // FIXME(eddyb) these are `Value`'s variants except `Const`, maybe `Use`
199    // should just use `Value` and assert it's never `Const`?
200    ControlRegionInput { region: ControlRegion, input_idx: u32 },
201    ControlNodeOutput { control_node: ControlNode, output_idx: u32 },
202    DataInstOutput(DataInst),
203
204    // NOTE(eddyb) these overlap somewhat with other cases, but they're always
205    // generated, even when there is no "use", for `multiversion` alignment.
206    AlignmentAnchorForControlRegion(ControlRegion),
207    AlignmentAnchorForControlNode(ControlNode),
208    AlignmentAnchorForDataInst(DataInst),
209}
210
211impl From<Value> for Use {
212    fn from(value: Value) -> Self {
213        match value {
214            Value::Const(ct) => Use::CxInterned(CxInterned::Const(ct)),
215            Value::ControlRegionInput { region, input_idx } => {
216                Use::ControlRegionInput { region, input_idx }
217            }
218            Value::ControlNodeOutput { control_node, output_idx } => {
219                Use::ControlNodeOutput { control_node, output_idx }
220            }
221            Value::DataInstOutput(inst) => Use::DataInstOutput(inst),
222        }
223    }
224}
225
226impl Use {
227    // HACK(eddyb) this is used in `AttrsAndDef::insert_name_before_def` to
228    // detect alignment anchors specifically, so it needs to not overlap with
229    // any other name (including those containing escaped `OpName` strings).
230    const ANCHOR_ALIGNMENT_NAME_PREFIX: &'static str = "AA.";
231
232    fn keyword_and_name_prefix(self) -> (&'static str, &'static str) {
233        match self {
234            Self::Node(node) => node.keyword_and_name_prefix().unwrap(),
235            Self::CxInterned(interned) => interned.keyword_and_name_prefix(),
236            Self::ControlRegionLabel(_) => ("label", "L"),
237
238            Self::ControlRegionInput { .. }
239            | Self::ControlNodeOutput { .. }
240            | Self::DataInstOutput(_) => ("", "v"),
241
242            Self::AlignmentAnchorForControlRegion(_)
243            | Self::AlignmentAnchorForControlNode(_)
244            | Self::AlignmentAnchorForDataInst(_) => ("", Self::ANCHOR_ALIGNMENT_NAME_PREFIX),
245        }
246    }
247}
248
249impl<'a> Plan<'a> {
250    /// Create a [`Plan`] with all of `root`'s dependencies, followed by `root` itself.
251    //
252    // FIXME(eddyb) consider renaming this and removing the `for_module` shorthand.
253    pub fn for_root(
254        cx: &'a Context,
255        root: &'a (impl Visit + Print<Output = pretty::Fragment>),
256    ) -> Self {
257        Self::for_versions(cx, [("", root)])
258    }
259
260    /// Create a [`Plan`] with all of `module`'s contents.
261    ///
262    /// Shorthand for `Plan::for_root(module.cx_ref(), module)`.
263    pub fn for_module(module: &'a Module) -> Self {
264        Self::for_root(module.cx_ref(), module)
265    }
266
267    /// Create a [`Plan`] that combines [`Plan::for_root`] from each version.
268    ///
269    /// Each version also has a string, which should contain a descriptive name
270    /// (e.g. the name of a pass that produced that version).
271    ///
272    /// While the roots (and their dependencies) can be entirely unrelated, the
273    /// output won't be very useful in that case. For ideal results, most of the
274    /// same entities (e.g. [`GlobalVar`] or [`Func`]) should be in most versions,
275    /// with most of the changes being limited to within their definitions.
276    pub fn for_versions(
277        cx: &'a Context,
278        versions: impl IntoIterator<
279            Item = (impl Into<String>, &'a (impl Visit + Print<Output = pretty::Fragment> + 'a)),
280        >,
281    ) -> Self {
282        let mut plan = Self {
283            cx,
284            current_module: None,
285            versions: vec![],
286            use_counts: FxIndexMap::default(),
287            attrs_to_unique_spv_name: FxHashMap::default(),
288            claimed_spv_names: FxHashMap::default(),
289        };
290        for (version_name, version_root) in versions {
291            let mut combined_use_counts = mem::take(&mut plan.use_counts);
292            let mut combined_attrs_to_unique_spv_name =
293                mem::take(&mut plan.attrs_to_unique_spv_name);
294            plan.claimed_spv_names.clear();
295
296            plan.versions.push(PlanVersion {
297                name: version_name.into(),
298                node_defs: FxHashMap::default(),
299                root: version_root,
300            });
301
302            version_root.visit_with(&mut plan);
303
304            // Merge use counts (from second version onward).
305            if !combined_use_counts.is_empty() {
306                for (use_kind, new_count) in plan.use_counts.drain(..) {
307                    let count = combined_use_counts.entry(use_kind).or_default();
308                    *count = new_count.max(*count);
309                }
310                plan.use_counts = combined_use_counts;
311            }
312
313            // Merge per-`AttrSet` unique `OpName`s (from second version onward).
314            if !combined_attrs_to_unique_spv_name.is_empty() {
315                for (attrs, unique_spv_name) in plan.attrs_to_unique_spv_name.drain() {
316                    let combined =
317                        combined_attrs_to_unique_spv_name.entry(attrs).or_insert(unique_spv_name);
318
319                    *combined = match (*combined, unique_spv_name) {
320                        // HACK(eddyb) can use pointer comparison because both
321                        // `OpName`s are in the same `AttrSetDef`'s `BTreeSet`,
322                        // so they can only be equal in the same set slot.
323                        (Ok(combined), Ok(new)) if std::ptr::eq(combined, new) => Ok(combined),
324
325                        _ => Err(AmbiguousName),
326                    };
327                }
328                plan.attrs_to_unique_spv_name = combined_attrs_to_unique_spv_name;
329            }
330        }
331
332        // HACK(eddyb) release memory used by this map (and avoid later misuse).
333        mem::take(&mut plan.claimed_spv_names);
334
335        plan
336    }
337
338    /// Add `interned` to the plan, after all of its dependencies.
339    ///
340    /// Only the first call recurses into the definition, subsequent calls only
341    /// update its (internally tracked) "use count".
342    fn use_interned(&mut self, interned: CxInterned) {
343        let use_kind = Use::CxInterned(interned);
344        if let Some(use_count) = self.use_counts.get_mut(&use_kind) {
345            *use_count += 1;
346            return;
347        }
348
349        match interned {
350            CxInterned::Type(ty) => {
351                self.visit_type_def(&self.cx[ty]);
352            }
353            CxInterned::Const(ct) => {
354                self.visit_const_def(&self.cx[ct]);
355            }
356        }
357
358        // Group all `CxInterned`s in a single top-level `Node`.
359        *self.use_counts.entry(Use::Node(Node::AllCxInterned)).or_default() += 1;
360
361        *self.use_counts.entry(use_kind).or_default() += 1;
362    }
363
364    /// Add `node` to the plan, after all of its dependencies.
365    ///
366    /// Only the first call recurses into the definition, subsequent calls only
367    /// update its (internally tracked) "use count".
368    fn use_node<D: Visit>(&mut self, node: Node, node_def: &'a D)
369    where
370        NodeDef<'a>: From<&'a D>,
371    {
372        if let Some(use_count) = self.use_counts.get_mut(&Use::Node(node)) {
373            *use_count += 1;
374            return;
375        }
376
377        let current_version = self.versions.last_mut().unwrap();
378        match current_version.node_defs.entry(node) {
379            Entry::Occupied(entry) => {
380                let old_ptr_eq_new = match (*entry.get(), NodeDef::from(node_def)) {
381                    (NodeDef::ModuleDialect(old), NodeDef::ModuleDialect(new)) => {
382                        std::ptr::eq(old, new)
383                    }
384                    (NodeDef::ModuleDebugInfo(old), NodeDef::ModuleDebugInfo(new)) => {
385                        std::ptr::eq(old, new)
386                    }
387                    (NodeDef::GlobalVar(old), NodeDef::GlobalVar(new)) => std::ptr::eq(old, new),
388                    (NodeDef::Func(old), NodeDef::Func(new)) => std::ptr::eq(old, new),
389                    _ => false,
390                };
391
392                // HACK(eddyb) this avoids infinite recursion - we can't insert
393                // into `use_counts` before `node_def.visit_with(self)` because
394                // we want dependencies to come before dependends, so recursion
395                // from the visitor (recursive `Func`s, or `visit_foo` calling
396                // `use_node` which calls the same `visit_foo` method again)
397                // ends up here, and we have to both allow it and early-return.
398                assert!(
399                    old_ptr_eq_new,
400                    "print: same `{}` node has multiple distinct definitions in `Plan`",
401                    node.keyword_and_name_prefix().map_or_else(|s| s, |(_, s)| s)
402                );
403                return;
404            }
405            Entry::Vacant(entry) => {
406                entry.insert(NodeDef::from(node_def));
407            }
408        }
409
410        node_def.visit_with(self);
411
412        *self.use_counts.entry(Use::Node(node)).or_default() += 1;
413    }
414}
415
416impl<'a> Visitor<'a> for Plan<'a> {
417    fn visit_attr_set_use(&mut self, attrs: AttrSet) {
418        let wk = &spv::spec::Spec::get().well_known;
419
420        let attrs_def = &self.cx[attrs];
421        self.visit_attr_set_def(attrs_def);
422
423        // Try to claim a SPIR-V `OpName`, if any are present in `attrs`.
424        let mut spv_names = attrs_def
425            .attrs
426            .iter()
427            .filter_map(|attr| match attr {
428                Attr::SpvAnnotation(spv_inst) if spv_inst.opcode == wk.OpName => Some(spv_inst),
429                _ => None,
430            })
431            .peekable();
432        if let Some(existing_entry) = self.attrs_to_unique_spv_name.get_mut(&attrs) {
433            // Same `attrs` seen more than once (from different definitions).
434            *existing_entry = Err(AmbiguousName);
435        } else if let Some(&first_spv_name) = spv_names.peek() {
436            let mut result = Ok(first_spv_name);
437
438            // HACK(eddyb) claim all SPIR-V `OpName`s in `attrs`, even if we'll
439            // use only one - this guarantees any overlaps will be detected, and
440            // while that may be overly strict, it's also the only easy way to
441            // have a completely order-indepdendent name choices.
442            for spv_name in spv_names {
443                let claim = self.claimed_spv_names.entry(spv_name).or_insert(Ok(attrs));
444
445                if let Ok(claimant) = *claim {
446                    if claimant == attrs {
447                        // Only possible way to succeed is if we just made the claim.
448                        continue;
449                    }
450
451                    // Invalidate the old user of this SPIR-V `OpName`.
452                    self.attrs_to_unique_spv_name.insert(claimant, Err(AmbiguousName));
453                }
454
455                // Either we just found a conflict, or one already existed.
456                *claim = Err(AmbiguousName);
457                result = Err(AmbiguousName);
458            }
459
460            self.attrs_to_unique_spv_name.insert(attrs, result);
461        }
462    }
463    fn visit_type_use(&mut self, ty: Type) {
464        self.use_interned(CxInterned::Type(ty));
465    }
466    fn visit_const_use(&mut self, ct: Const) {
467        self.use_interned(CxInterned::Const(ct));
468    }
469    fn visit_data_inst_form_use(&mut self, data_inst_form: DataInstForm) {
470        // NOTE(eddyb) this contains no deduplication because each `DataInstDef`
471        // will be pretty-printed separately, so everything in its `form` also
472        // needs to get use counts incremented separately per-`DataInstDef`.
473        self.visit_data_inst_form_def(&self.cx[data_inst_form]);
474    }
475
476    fn visit_global_var_use(&mut self, gv: GlobalVar) {
477        if let Some(module) = self.current_module {
478            self.use_node(Node::GlobalVar(gv), &module.global_vars[gv]);
479        } else {
480            // FIXME(eddyb) should this be a hard error?
481        }
482    }
483
484    fn visit_func_use(&mut self, func: Func) {
485        if let Some(module) = self.current_module {
486            self.use_node(Node::Func(func), &module.funcs[func]);
487        } else {
488            // FIXME(eddyb) should this be a hard error?
489        }
490    }
491
492    fn visit_module(&mut self, module: &'a Module) {
493        assert!(
494            std::ptr::eq(self.cx, &**module.cx_ref()),
495            "print: `Plan::visit_module` does not support `Module`s from a \
496             different `Context` than the one it was initially created with",
497        );
498
499        let old_module = self.current_module.replace(module);
500        module.inner_visit_with(self);
501        self.current_module = old_module;
502    }
503    fn visit_module_dialect(&mut self, dialect: &'a ModuleDialect) {
504        self.use_node(Node::ModuleDialect, dialect);
505    }
506    fn visit_module_debug_info(&mut self, debug_info: &'a ModuleDebugInfo) {
507        self.use_node(Node::ModuleDebugInfo, debug_info);
508    }
509
510    fn visit_attr(&mut self, attr: &'a Attr) {
511        attr.inner_visit_with(self);
512
513        // HACK(eddyb) the interpolated parts aren't visited by default
514        // (as they're "inert data").
515        if let Attr::Diagnostics(OrdAssertEq(diags)) = attr {
516            for diag in diags {
517                let Diag { level, message } = diag;
518                match level {
519                    DiagLevel::Bug(_) | DiagLevel::Error | DiagLevel::Warning => {}
520                }
521                message.inner_visit_with(self);
522            }
523        }
524    }
525
526    fn visit_const_def(&mut self, ct_def: &'a ConstDef) {
527        // HACK(eddyb) the type of a `PtrToGlobalVar` is never printed, skip it.
528        if let ConstKind::PtrToGlobalVar(gv) = ct_def.kind {
529            self.visit_attr_set_use(ct_def.attrs);
530            self.visit_global_var_use(gv);
531        } else {
532            ct_def.inner_visit_with(self);
533        }
534    }
535
536    fn visit_global_var_decl(&mut self, gv_decl: &'a GlobalVarDecl) {
537        // HACK(eddyb) get the pointee type from SPIR-V `OpTypePointer`, but
538        // ideally the `GlobalVarDecl` would hold that type itself.
539        let pointee_type = {
540            let wk = &spv::spec::Spec::get().well_known;
541
542            match &self.cx[gv_decl.type_of_ptr_to].kind {
543                TypeKind::SpvInst { spv_inst, type_and_const_inputs }
544                    if spv_inst.opcode == wk.OpTypePointer =>
545                {
546                    match type_and_const_inputs[..] {
547                        [TypeOrConst::Type(ty)] => Some(ty),
548                        _ => unreachable!(),
549                    }
550                }
551                _ => None,
552            }
553        };
554
555        // HACK(eddyb) if we can get away without visiting the `OpTypePointer`
556        // `type_of_ptr_to`, but only its pointee type, do so to avoid spurious
557        // `OpTypePointer` types showing up in the pretty-printed output.
558        match (gv_decl, pointee_type) {
559            (
560                GlobalVarDecl {
561                    attrs,
562                    type_of_ptr_to: _,
563                    shape: None,
564                    addr_space: AddrSpace::SpvStorageClass(_),
565                    def,
566                },
567                Some(pointee_type),
568            ) => {
569                self.visit_attr_set_use(*attrs);
570                self.visit_type_use(pointee_type);
571                def.inner_visit_with(self);
572            }
573
574            _ => {
575                gv_decl.inner_visit_with(self);
576            }
577        }
578    }
579
580    fn visit_func_decl(&mut self, func_decl: &'a FuncDecl) {
581        if let DeclDef::Present(func_def_body) = &func_decl.def {
582            if let Some(cfg) = &func_def_body.unstructured_cfg {
583                for region in cfg.rev_post_order(func_def_body) {
584                    if let Some(control_inst) = cfg.control_inst_on_exit_from.get(region) {
585                        for &target in &control_inst.targets {
586                            *self.use_counts.entry(Use::ControlRegionLabel(target)).or_default() +=
587                                1;
588                        }
589                    }
590                }
591            }
592        }
593
594        func_decl.inner_visit_with(self);
595    }
596
597    fn visit_value_use(&mut self, v: &'a Value) {
598        match *v {
599            Value::Const(_) => {}
600            _ => *self.use_counts.entry(Use::from(*v)).or_default() += 1,
601        }
602        v.inner_visit_with(self);
603    }
604}
605
606// FIXME(eddyb) make max line width configurable.
607const MAX_LINE_WIDTH: usize = 120;
608
609impl Plan<'_> {
610    #[allow(rustdoc::private_intra_doc_links)]
611    /// Print the whole [`Plan`] to a [`Versions<pretty::Fragment>`] and perform
612    /// layout on its [`pretty::Fragment`]s.
613    ///
614    /// The resulting [`Versions<pretty::FragmentPostLayout>`] value supports
615    /// [`fmt::Display`] for convenience, but also more specific methods
616    /// (e.g. HTML output).
617    pub fn pretty_print(&self) -> Versions<pretty::FragmentPostLayout> {
618        self.print(&Printer::new(self))
619            .map_pretty_fragments(|fragment| fragment.layout_with_max_line_width(MAX_LINE_WIDTH))
620    }
621
622    /// Like `pretty_print`, but separately pretty-printing "root dependencies"
623    /// and the "root" itself (useful for nesting pretty-printed SPIR-T elsewhere).
624    pub fn pretty_print_deps_and_root_separately(
625        &self,
626    ) -> (Versions<pretty::FragmentPostLayout>, Versions<pretty::FragmentPostLayout>) {
627        let printer = Printer::new(self);
628        (
629            self.print_all_nodes_and_or_root(&printer, true, false).map_pretty_fragments(
630                |fragment| fragment.layout_with_max_line_width(MAX_LINE_WIDTH),
631            ),
632            self.print_all_nodes_and_or_root(&printer, false, true).map_pretty_fragments(
633                |fragment| fragment.layout_with_max_line_width(MAX_LINE_WIDTH),
634            ),
635        )
636    }
637}
638
639pub struct Printer<'a> {
640    cx: &'a Context,
641    use_styles: FxIndexMap<Use, UseStyle>,
642
643    /// Subset of the `Plan`'s original `attrs_to_unique_spv_name` map, only
644    /// containing those entries which are actively used for `UseStyle::Named`
645    /// values in `use_styles`, and therefore need to be hidden from attributes.
646    attrs_with_spv_name_in_use: FxHashMap<AttrSet, &'a spv::Inst>,
647}
648
649/// How an [`Use`] of a definition should be printed.
650enum UseStyle {
651    /// Refer to the definition by its name prefix and an `idx` (e.g. "T123").
652    Anon {
653        /// For intra-function [`Use`]s (i.e. [`Use::ControlRegionLabel`] and values),
654        /// this disambiguates the parent function (for e.g. anchors).
655        parent_func: Option<Func>,
656
657        idx: usize,
658    },
659
660    /// Refer to the definition by its name prefix and a `name` (e.g. "T`Foo`").
661    Named {
662        /// For intra-function [`Use`]s (i.e. [`Use::ControlRegionLabel`] and values),
663        /// this disambiguates the parent function (for e.g. anchors).
664        parent_func: Option<Func>,
665
666        name: String,
667    },
668
669    /// Print the definition inline at the use site.
670    Inline,
671}
672
673impl<'a> Printer<'a> {
674    fn new(plan: &Plan<'a>) -> Self {
675        let cx = plan.cx;
676        let wk = &spv::spec::Spec::get().well_known;
677
678        // HACK(eddyb) move this elsewhere.
679        enum SmallSet<T, const N: usize> {
680            Linear(ArrayVec<T, N>),
681            Hashed(Box<FxIndexSet<T>>),
682        }
683
684        type SmallSetIter<'a, T> = Either<std::slice::Iter<'a, T>, indexmap::set::Iter<'a, T>>;
685
686        impl<T, const N: usize> Default for SmallSet<T, N> {
687            fn default() -> Self {
688                Self::Linear(ArrayVec::new())
689            }
690        }
691
692        impl<T: Eq + Hash, const N: usize> SmallSet<T, N> {
693            fn insert(&mut self, x: T) {
694                match self {
695                    Self::Linear(xs) => {
696                        // HACK(eddyb) this optimizes for values repeating, i.e.
697                        // `xs.last() == Some(&x)` being the most common case.
698                        if !xs.iter().rev().any(|old| *old == x) {
699                            if let Err(err) = xs.try_push(x) {
700                                *self = Self::Hashed(Box::new(
701                                    xs.drain(..).chain([err.element()]).collect(),
702                                ));
703                            }
704                        }
705                    }
706                    Self::Hashed(xs) => {
707                        xs.insert(x);
708                    }
709                }
710            }
711
712            fn iter(&self) -> SmallSetIter<'_, T> {
713                match self {
714                    Self::Linear(xs) => Either::Left(xs.iter()),
715                    Self::Hashed(xs) => Either::Right(xs.iter()),
716                }
717            }
718        }
719
720        let mut attrs_with_spv_name_in_use = FxHashMap::default();
721
722        // NOTE(eddyb) `SmallSet` is an important optimization, as most attributes
723        // *do not* change across versions, so we avoid a lot of repeated work.
724        let mut try_claim_name_from_attrs_across_versions =
725            |deduped_attrs_across_versions: SmallSetIter<'_, AttrSet>| {
726                deduped_attrs_across_versions
727                    .copied()
728                    .map(|attrs| Some((attrs, plan.attrs_to_unique_spv_name.get(&attrs)?.ok()?)))
729                    .collect::<Option<SmallVec<[_; 4]>>>()
730                    .filter(|all_names| all_names.iter().map(|(_, spv_name)| spv_name).all_equal())
731                    .and_then(|all_names| {
732                        let &(_, spv_name) = all_names.first()?;
733                        let name = spv::extract_literal_string(&spv_name.imms).ok()?;
734
735                        // This is the point of no return: these `insert`s will
736                        // cause `OpName`s to be hidden from their `AttrSet`s.
737                        for (attrs, spv_name) in all_names {
738                            attrs_with_spv_name_in_use.insert(attrs, spv_name);
739                        }
740
741                        Some(name)
742                    })
743            };
744
745        #[derive(Default)]
746        struct AnonCounters {
747            types: usize,
748            consts: usize,
749
750            global_vars: usize,
751            funcs: usize,
752        }
753        let mut anon_counters = AnonCounters::default();
754
755        let mut use_styles: FxIndexMap<_, _> = plan
756            .use_counts
757            .iter()
758            .map(|(&use_kind, &use_count)| {
759                // HACK(eddyb) these are assigned later.
760                if let Use::ControlRegionLabel(_)
761                | Use::ControlRegionInput { .. }
762                | Use::ControlNodeOutput { .. }
763                | Use::DataInstOutput(_) = use_kind
764                {
765                    return (use_kind, UseStyle::Inline);
766                }
767
768                // HACK(eddyb) these are "global" to the whole print `Plan`.
769                if let Use::Node(
770                    Node::AllCxInterned | Node::ModuleDialect | Node::ModuleDebugInfo,
771                ) = use_kind
772                {
773                    return (use_kind, UseStyle::Anon { parent_func: None, idx: 0 });
774                }
775
776                let mut deduped_attrs_across_versions = SmallSet::<_, 8>::default();
777                match use_kind {
778                    Use::CxInterned(interned) => {
779                        deduped_attrs_across_versions.insert(match interned {
780                            CxInterned::Type(ty) => cx[ty].attrs,
781                            CxInterned::Const(ct) => cx[ct].attrs,
782                        });
783                    }
784                    Use::Node(node) => {
785                        for version in &plan.versions {
786                            let attrs = match version.node_defs.get(&node) {
787                                Some(NodeDef::GlobalVar(gv_decl)) => gv_decl.attrs,
788                                Some(NodeDef::Func(func_decl)) => func_decl.attrs,
789                                _ => continue,
790                            };
791                            deduped_attrs_across_versions.insert(attrs);
792                        }
793                    }
794                    Use::ControlRegionLabel(_)
795                    | Use::ControlRegionInput { .. }
796                    | Use::ControlNodeOutput { .. }
797                    | Use::DataInstOutput(_)
798                    | Use::AlignmentAnchorForControlRegion(_)
799                    | Use::AlignmentAnchorForControlNode(_)
800                    | Use::AlignmentAnchorForDataInst(_) => unreachable!(),
801                }
802
803                if let Some(name) =
804                    try_claim_name_from_attrs_across_versions(deduped_attrs_across_versions.iter())
805                {
806                    return (use_kind, UseStyle::Named { parent_func: None, name });
807                }
808
809                let inline = match use_kind {
810                    Use::CxInterned(interned) => {
811                        use_count == 1
812                            || match interned {
813                                CxInterned::Type(ty) => {
814                                    let ty_def = &cx[ty];
815
816                                    // FIXME(eddyb) remove the duplication between
817                                    // here and `TypeDef`'s `Print` impl.
818                                    let has_compact_print_or_is_leaf = match &ty_def.kind {
819                                        TypeKind::SpvInst { spv_inst, type_and_const_inputs } => {
820                                            [
821                                                wk.OpTypeBool,
822                                                wk.OpTypeInt,
823                                                wk.OpTypeFloat,
824                                                wk.OpTypeVector,
825                                            ]
826                                            .contains(&spv_inst.opcode)
827                                                || type_and_const_inputs.is_empty()
828                                        }
829
830                                        TypeKind::QPtr | TypeKind::SpvStringLiteralForExtInst => {
831                                            true
832                                        }
833                                    };
834
835                                    ty_def.attrs == AttrSet::default()
836                                        && has_compact_print_or_is_leaf
837                                }
838                                CxInterned::Const(ct) => {
839                                    let ct_def = &cx[ct];
840
841                                    // FIXME(eddyb) remove the duplication between
842                                    // here and `ConstDef`'s `Print` impl.
843                                    let (has_compact_print, has_nested_consts) = match &ct_def.kind
844                                    {
845                                        ConstKind::SpvInst { spv_inst_and_const_inputs } => {
846                                            let (spv_inst, const_inputs) =
847                                                &**spv_inst_and_const_inputs;
848                                            (
849                                                [
850                                                    wk.OpConstantFalse,
851                                                    wk.OpConstantTrue,
852                                                    wk.OpConstant,
853                                                ]
854                                                .contains(&spv_inst.opcode),
855                                                !const_inputs.is_empty(),
856                                            )
857                                        }
858                                        _ => (false, false),
859                                    };
860
861                                    ct_def.attrs == AttrSet::default()
862                                        && (has_compact_print || !has_nested_consts)
863                                }
864                            }
865                    }
866                    Use::Node(_) => false,
867
868                    Use::ControlRegionLabel(_)
869                    | Use::ControlRegionInput { .. }
870                    | Use::ControlNodeOutput { .. }
871                    | Use::DataInstOutput(_)
872                    | Use::AlignmentAnchorForControlRegion(_)
873                    | Use::AlignmentAnchorForControlNode(_)
874                    | Use::AlignmentAnchorForDataInst(_) => {
875                        unreachable!()
876                    }
877                };
878                let style = if inline {
879                    UseStyle::Inline
880                } else {
881                    let ac = &mut anon_counters;
882                    let counter = match use_kind {
883                        Use::CxInterned(CxInterned::Type(_)) => &mut ac.types,
884                        Use::CxInterned(CxInterned::Const(_)) => &mut ac.consts,
885                        Use::Node(Node::GlobalVar(_)) => &mut ac.global_vars,
886                        Use::Node(Node::Func(_)) => &mut ac.funcs,
887
888                        Use::Node(
889                            Node::AllCxInterned | Node::ModuleDialect | Node::ModuleDebugInfo,
890                        )
891                        | Use::ControlRegionLabel(_)
892                        | Use::ControlRegionInput { .. }
893                        | Use::ControlNodeOutput { .. }
894                        | Use::DataInstOutput(_)
895                        | Use::AlignmentAnchorForControlRegion(_)
896                        | Use::AlignmentAnchorForControlNode(_)
897                        | Use::AlignmentAnchorForDataInst(_) => {
898                            unreachable!()
899                        }
900                    };
901                    let idx = *counter;
902                    *counter += 1;
903                    UseStyle::Anon { parent_func: None, idx }
904                };
905                (use_kind, style)
906            })
907            .collect();
908
909        let all_funcs = plan.use_counts.keys().filter_map(|&use_kind| match use_kind {
910            Use::Node(Node::Func(func)) => Some(func),
911            _ => None,
912        });
913        for func in all_funcs {
914            assert!(matches!(
915                use_styles.get(&Use::Node(Node::Func(func))),
916                Some(UseStyle::Anon { .. } | UseStyle::Named { .. })
917            ));
918
919            // HACK(eddyb) in order to claim `OpName`s unambiguously for any
920            // intra-function `Use`, we need its attrs from *all* versions, at
921            // the same time, but we visit each version's `FuncDefBody` one at
922            // a time, and `EntityDefs` (intentionally) bans even checking if
923            // some entity is defined at all, so we can't rely on random-access,
924            // and we have to first buffer all the intra-function definitions.
925            #[derive(Default)]
926            struct IntraFuncDefAcrossVersions {
927                deduped_attrs_across_versions: SmallSet<AttrSet, 4>,
928            }
929            let mut intra_func_defs_across_versions: FxIndexMap<Use, IntraFuncDefAcrossVersions> =
930                FxIndexMap::default();
931
932            let func_def_bodies_across_versions = plan.versions.iter().filter_map(|version| {
933                match version.node_defs.get(&Node::Func(func))? {
934                    NodeDef::Func(FuncDecl { def: DeclDef::Present(func_def_body), .. }) => {
935                        Some(func_def_body)
936                    }
937
938                    _ => None,
939                }
940            });
941
942            for func_def_body in func_def_bodies_across_versions {
943                let mut define = |use_kind, attrs| {
944                    let def = intra_func_defs_across_versions.entry(use_kind).or_default();
945                    if let Some(attrs) = attrs {
946                        def.deduped_attrs_across_versions.insert(attrs);
947                    }
948                };
949                let visit_region = |func_at_region: FuncAt<'_, ControlRegion>| {
950                    let region = func_at_region.position;
951
952                    define(Use::AlignmentAnchorForControlRegion(region), None);
953                    // FIXME(eddyb) should labels have names?
954                    define(Use::ControlRegionLabel(region), None);
955
956                    let ControlRegionDef { inputs, children, outputs: _ } =
957                        func_def_body.at(region).def();
958
959                    for (i, input_decl) in inputs.iter().enumerate() {
960                        define(
961                            Use::ControlRegionInput { region, input_idx: i.try_into().unwrap() },
962                            Some(input_decl.attrs),
963                        );
964                    }
965
966                    for func_at_control_node in func_def_body.at(*children) {
967                        let control_node = func_at_control_node.position;
968
969                        define(Use::AlignmentAnchorForControlNode(control_node), None);
970
971                        let ControlNodeDef { kind, outputs } = func_at_control_node.def();
972
973                        if let ControlNodeKind::Block { insts } = *kind {
974                            for func_at_inst in func_def_body.at(insts) {
975                                define(
976                                    Use::AlignmentAnchorForDataInst(func_at_inst.position),
977                                    None,
978                                );
979                                let inst_def = func_at_inst.def();
980                                if cx[inst_def.form].output_type.is_some() {
981                                    define(
982                                        Use::DataInstOutput(func_at_inst.position),
983                                        Some(inst_def.attrs),
984                                    );
985                                }
986                            }
987                        }
988
989                        for (i, output_decl) in outputs.iter().enumerate() {
990                            define(
991                                Use::ControlNodeOutput {
992                                    control_node,
993                                    output_idx: i.try_into().unwrap(),
994                                },
995                                Some(output_decl.attrs),
996                            );
997                        }
998                    }
999                };
1000
1001                // FIXME(eddyb) maybe this should be provided by `visit`.
1002                struct VisitAllRegions<F>(F);
1003                impl<'a, F: FnMut(FuncAt<'a, ControlRegion>)> Visitor<'a> for VisitAllRegions<F> {
1004                    // FIXME(eddyb) this is excessive, maybe different kinds of
1005                    // visitors should exist for module-level and func-level?
1006                    fn visit_attr_set_use(&mut self, _: AttrSet) {}
1007                    fn visit_type_use(&mut self, _: Type) {}
1008                    fn visit_const_use(&mut self, _: Const) {}
1009                    fn visit_data_inst_form_use(&mut self, _: DataInstForm) {}
1010                    fn visit_global_var_use(&mut self, _: GlobalVar) {}
1011                    fn visit_func_use(&mut self, _: Func) {}
1012
1013                    fn visit_control_region_def(
1014                        &mut self,
1015                        func_at_control_region: FuncAt<'a, ControlRegion>,
1016                    ) {
1017                        self.0(func_at_control_region);
1018                        func_at_control_region.inner_visit_with(self);
1019                    }
1020                }
1021                func_def_body.inner_visit_with(&mut VisitAllRegions(visit_region));
1022            }
1023
1024            let mut control_region_label_counter = 0;
1025            let mut value_counter = 0;
1026            let mut alignment_anchor_counter = 0;
1027
1028            // Assign an unique label/value/alignment-anchor name/index to each
1029            // intra-function definition which appears in at least one version,
1030            // but only if it's actually used (or is an alignment anchor).
1031            for (use_kind, def) in intra_func_defs_across_versions {
1032                let (counter, use_style_slot) = match use_kind {
1033                    Use::ControlRegionLabel(_) => {
1034                        (&mut control_region_label_counter, use_styles.get_mut(&use_kind))
1035                    }
1036
1037                    Use::ControlRegionInput { .. }
1038                    | Use::ControlNodeOutput { .. }
1039                    | Use::DataInstOutput(_) => (&mut value_counter, use_styles.get_mut(&use_kind)),
1040
1041                    Use::AlignmentAnchorForControlRegion(_)
1042                    | Use::AlignmentAnchorForControlNode(_)
1043                    | Use::AlignmentAnchorForDataInst(_) => (
1044                        &mut alignment_anchor_counter,
1045                        Some(use_styles.entry(use_kind).or_insert(UseStyle::Inline)),
1046                    ),
1047
1048                    _ => unreachable!(),
1049                };
1050                if let Some(use_style) = use_style_slot {
1051                    assert!(matches!(use_style, UseStyle::Inline));
1052
1053                    let parent_func = Some(func);
1054                    let named_style = try_claim_name_from_attrs_across_versions(
1055                        def.deduped_attrs_across_versions.iter(),
1056                    )
1057                    .map(|name| UseStyle::Named { parent_func, name });
1058
1059                    *use_style = named_style.unwrap_or_else(|| {
1060                        let idx = *counter;
1061                        *counter += 1;
1062                        UseStyle::Anon { parent_func, idx }
1063                    });
1064                }
1065            }
1066        }
1067
1068        Self { cx, use_styles, attrs_with_spv_name_in_use }
1069    }
1070
1071    pub fn cx(&self) -> &'a Context {
1072        self.cx
1073    }
1074}
1075
1076// Styles for a variety of syntactic categories.
1077// FIXME(eddyb) this is a somewhat inefficient way of declaring these.
1078//
1079// NOTE(eddyb) these methods take `self` so they can become configurable in the future.
1080#[allow(clippy::unused_self)]
1081impl Printer<'_> {
1082    fn error_style(&self) -> pretty::Styles {
1083        pretty::Styles::color(pretty::palettes::simple::MAGENTA)
1084    }
1085    fn comment_style(&self) -> pretty::Styles {
1086        pretty::Styles {
1087            color_opacity: Some(0.3),
1088            size: Some(-4),
1089            // FIXME(eddyb) this looks wrong for some reason?
1090            // subscript: true,
1091            ..pretty::Styles::color(pretty::palettes::simple::DARK_GRAY)
1092        }
1093    }
1094    fn named_argument_label_style(&self) -> pretty::Styles {
1095        pretty::Styles {
1096            size: Some(-5),
1097            ..pretty::Styles::color(pretty::palettes::simple::DARK_GRAY)
1098        }
1099    }
1100    fn numeric_literal_style(&self) -> pretty::Styles {
1101        pretty::Styles::color(pretty::palettes::simple::YELLOW)
1102    }
1103    fn string_literal_style(&self) -> pretty::Styles {
1104        pretty::Styles::color(pretty::palettes::simple::RED)
1105    }
1106    fn string_literal_escape_style(&self) -> pretty::Styles {
1107        pretty::Styles::color(pretty::palettes::simple::ORANGE)
1108    }
1109    fn declarative_keyword_style(&self) -> pretty::Styles {
1110        pretty::Styles::color(pretty::palettes::simple::BLUE)
1111    }
1112    fn imperative_keyword_style(&self) -> pretty::Styles {
1113        pretty::Styles {
1114            thickness: Some(2),
1115            ..pretty::Styles::color(pretty::palettes::simple::MAGENTA)
1116        }
1117    }
1118    fn spv_base_style(&self) -> pretty::Styles {
1119        pretty::Styles::color(pretty::palettes::simple::ORANGE)
1120    }
1121    fn spv_op_style(&self) -> pretty::Styles {
1122        pretty::Styles { thickness: Some(3), ..self.spv_base_style() }
1123    }
1124    fn spv_enumerand_name_style(&self) -> pretty::Styles {
1125        pretty::Styles::color(pretty::palettes::simple::CYAN)
1126    }
1127    fn attr_style(&self) -> pretty::Styles {
1128        pretty::Styles {
1129            color_opacity: Some(0.6),
1130            thickness: Some(-2),
1131            ..pretty::Styles::color(pretty::palettes::simple::GREEN)
1132        }
1133    }
1134
1135    /// Compute a suitable style for an unintrusive `foo.` "namespace prefix",
1136    /// from a more typical style (by shrinking and/or reducing visibility).
1137    fn demote_style_for_namespace_prefix(&self, mut style: pretty::Styles) -> pretty::Styles {
1138        // NOTE(eddyb) this was `opacity: Some(0.4)` + `thickness: Some(-3)`,
1139        // but thinner text ended up being more annoying to read while still
1140        // using up too much real-estate (compared to decreasing the size).
1141        style.color_opacity = Some(style.color_opacity.unwrap_or(1.0) * 0.6);
1142        // FIXME(eddyb) maybe this could be more uniform with a different unit.
1143        style.size = Some(style.size.map_or(-4, |size| size - 1));
1144        style
1145    }
1146}
1147
1148impl Printer<'_> {
1149    /// Pretty-print a string literal with escaping and styling.
1150    //
1151    // FIXME(eddyb) add methods like this for all styled text (e.g. numeric literals).
1152    fn pretty_string_literal(&self, s: &str) -> pretty::Fragment {
1153        // HACK(eddyb) this is somewhat inefficient, but we need to allocate a
1154        // `String` for every piece anyway, so might as well make it convenient.
1155        pretty::Fragment::new(
1156            // HACK(eddyb) this allows aligning the actual string contents,
1157            // (see `c == '\n'` special-casing below for when this applies).
1158            (s.contains('\n').then_some(Either::Left(' ')).into_iter())
1159                .chain([Either::Left('"')])
1160                .chain(s.chars().flat_map(|c| {
1161                    let escaped = c.escape_debug();
1162                    let maybe_escaped = if c == '\'' {
1163                        // Unescape single quotes, we're in a double-quoted string.
1164                        assert_eq!(escaped.collect_tuple(), Some(('\\', c)));
1165                        Either::Left(c)
1166                    } else if let Some((single,)) = escaped.clone().collect_tuple() {
1167                        assert_eq!(single, c);
1168                        Either::Left(c)
1169                    } else {
1170                        assert_eq!(escaped.clone().next(), Some('\\'));
1171                        Either::Right(escaped)
1172                    };
1173
1174                    // HACK(eddyb) move escaped `\n` to the start of a new line,
1175                    // using Rust's trailing `\` on the previous line, which eats
1176                    // all following whitespace (and only stops at the escape).
1177                    let extra_prefix_unescaped = if c == '\n' { "\\\n" } else { "" };
1178
1179                    (extra_prefix_unescaped.chars().map(Either::Left)).chain([maybe_escaped])
1180                }))
1181                .chain([Either::Left('"')])
1182                .group_by(|maybe_escaped| maybe_escaped.is_right())
1183                .into_iter()
1184                .map(|(escaped, group)| {
1185                    if escaped {
1186                        self.string_literal_escape_style()
1187                            .apply(group.flat_map(Either::unwrap_right).collect::<String>())
1188                    } else {
1189                        self.string_literal_style()
1190                            .apply(group.map(Either::unwrap_left).collect::<String>())
1191                    }
1192                }),
1193        )
1194    }
1195
1196    /// Pretty-print a `name: ` style "named argument" prefix.
1197    fn pretty_named_argument_prefix<'b>(&self, name: impl Into<Cow<'b, str>>) -> pretty::Fragment {
1198        // FIXME(eddyb) avoid the cost of allocating here.
1199        self.named_argument_label_style().apply(format!("{}: ", name.into())).into()
1200    }
1201
1202    /// Pretty-print a `: T` style "type ascription" suffix.
1203    ///
1204    /// This should be used everywhere some type ascription notation is needed,
1205    /// to ensure consistency across all such situations.
1206    fn pretty_type_ascription_suffix(&self, ty: Type) -> pretty::Fragment {
1207        pretty::join_space(":", [ty.print(self)])
1208    }
1209
1210    /// Pretty-print a SPIR-V `opcode`'s name, prefixed by `"spv."`.
1211    fn pretty_spv_opcode(
1212        &self,
1213        opcode_name_style: pretty::Styles,
1214        opcode: spv::spec::Opcode,
1215    ) -> pretty::Fragment {
1216        pretty::Fragment::new([
1217            self.demote_style_for_namespace_prefix(self.spv_base_style()).apply("spv."),
1218            opcode_name_style.apply(opcode.name()),
1219        ])
1220    }
1221
1222    /// Clean up a `spv::print::TokensForOperand` string (common helper used below).
1223    #[allow(clippy::unused_self)]
1224    fn sanitize_spv_operand_name<'b>(&self, name: &'b str) -> Option<Cow<'b, str>> {
1225        Some(name).and_then(|name| {
1226            // HACK(eddyb) some operand names are useless.
1227            if name == "Type"
1228                || name
1229                    .strip_prefix("Operand ")
1230                    .is_some_and(|s| s.chars().all(|c| c.is_ascii_digit()))
1231            {
1232                return None;
1233            }
1234
1235            // Turn `Foo Bar` and `Foo bar` into `FooBar`.
1236            // FIXME(eddyb) use `&[AsciiChar]` when that stabilizes.
1237            let name = name
1238                .split_ascii_whitespace()
1239                .map(|word| {
1240                    if word.starts_with(|c: char| c.is_ascii_lowercase()) {
1241                        let mut word = word.to_string();
1242                        word[..1].make_ascii_uppercase();
1243                        Cow::Owned(word)
1244                    } else {
1245                        word.into()
1246                    }
1247                })
1248                .reduce(|out, extra| (out.into_owned() + &extra).into())
1249                .unwrap_or_default();
1250
1251            Some(name)
1252        })
1253    }
1254
1255    /// Pretty-print a `spv::print::TokensForOperand` (common helper used below).
1256    fn pretty_spv_print_tokens_for_operand(
1257        &self,
1258        operand: spv::print::TokensForOperand<Option<pretty::Fragment>>,
1259    ) -> pretty::Fragment {
1260        pretty::Fragment::new(operand.tokens.into_iter().map(|token| {
1261            match token {
1262                spv::print::Token::Error(s) => self.error_style().apply(s).into(),
1263                spv::print::Token::OperandName(s) => self
1264                    .sanitize_spv_operand_name(s)
1265                    .map(|name| self.pretty_named_argument_prefix(name))
1266                    .unwrap_or_default(),
1267                spv::print::Token::Punctuation(s) => s.into(),
1268                spv::print::Token::OperandKindNamespacePrefix(s) => {
1269                    pretty::Fragment::new([
1270                        // HACK(eddyb) double-demote to end up with `spv.A.B`,
1271                        // with increasing size from `spv.` to `A.` to `B`.
1272                        self.demote_style_for_namespace_prefix(
1273                            self.demote_style_for_namespace_prefix(self.spv_base_style()),
1274                        )
1275                        .apply("spv."),
1276                        // FIXME(eddyb) avoid the cost of allocating here.
1277                        self.demote_style_for_namespace_prefix(self.declarative_keyword_style())
1278                            .apply(format!("{s}.")),
1279                    ])
1280                }
1281                spv::print::Token::EnumerandName(s) => {
1282                    self.spv_enumerand_name_style().apply(s).into()
1283                }
1284                spv::print::Token::NumericLiteral(s) => {
1285                    self.numeric_literal_style().apply(s).into()
1286                }
1287                spv::print::Token::StringLiteral(s) => self.string_literal_style().apply(s).into(),
1288                spv::print::Token::Id(id) => {
1289                    id.unwrap_or_else(|| self.comment_style().apply("/* implicit ID */").into())
1290                }
1291            }
1292        }))
1293    }
1294
1295    /// Pretty-print a single SPIR-V operand from only immediates, potentially
1296    /// composed of an enumerand with parameters (which consumes more immediates).
1297    fn pretty_spv_operand_from_imms(
1298        &self,
1299        imms: impl IntoIterator<Item = spv::Imm>,
1300    ) -> pretty::Fragment {
1301        self.pretty_spv_print_tokens_for_operand(spv::print::operand_from_imms(imms))
1302    }
1303
1304    /// Pretty-print a single SPIR-V (short) immediate (e.g. an enumerand).
1305    fn pretty_spv_imm(&self, kind: spv::spec::OperandKind, word: u32) -> pretty::Fragment {
1306        self.pretty_spv_operand_from_imms([spv::Imm::Short(kind, word)])
1307    }
1308
1309    /// Pretty-print an arbitrary SPIR-V `opcode` with its SPIR-V operands being
1310    /// given by `imms` (non-IDs) and `printed_ids` (IDs, printed by the caller).
1311    ///
1312    /// `printed_ids` elements can be `None` to indicate an ID operand is implicit
1313    /// in SPIR-T, and should not be printed (e.g. decorations' target IDs).
1314    /// But if `printed_ids` doesn't need to have `None` elements, it can skip
1315    /// the `Option` entirely (i.e. have `pretty::Fragment` elements directly).
1316    ///
1317    /// Immediate and `ID` operands are interleaved (in the order mandated by
1318    /// the SPIR-V standard) and together wrapped in parentheses, e.g.:
1319    /// `spv.OpFoo(spv.FooEnum.Bar, v1, 123, v2, "baz")`.
1320    ///
1321    /// This should be used everywhere a SPIR-V instruction needs to be printed,
1322    /// to ensure consistency across all such situations.
1323    fn pretty_spv_inst<OPF: Into<Option<pretty::Fragment>>>(
1324        &self,
1325        spv_inst_name_style: pretty::Styles,
1326        opcode: spv::spec::Opcode,
1327        imms: &[spv::Imm],
1328        printed_ids: impl IntoIterator<Item = OPF>,
1329    ) -> pretty::Fragment {
1330        let mut operands = spv::print::inst_operands(
1331            opcode,
1332            imms.iter().copied(),
1333            printed_ids.into_iter().map(|printed_id| printed_id.into()),
1334        )
1335        .filter_map(|operand| match operand.tokens[..] {
1336            [spv::print::Token::Id(None)]
1337            | [spv::print::Token::OperandName(_), spv::print::Token::Id(None)] => None,
1338
1339            _ => Some(self.pretty_spv_print_tokens_for_operand(operand)),
1340        })
1341        .peekable();
1342
1343        let mut out = self.pretty_spv_opcode(spv_inst_name_style, opcode);
1344
1345        if operands.peek().is_some() {
1346            out = pretty::Fragment::new([out, pretty::join_comma_sep("(", operands, ")")]);
1347        }
1348
1349        out
1350    }
1351}
1352
1353/// A [`Print`] `Output` type that splits the attributes from the main body of the
1354/// definition, allowing additional processing before they get concatenated.
1355#[derive(Default)]
1356pub struct AttrsAndDef {
1357    pub attrs: pretty::Fragment,
1358
1359    /// Definition that typically looks like one of these cases:
1360    /// * ` = ...` for `name = ...`
1361    /// * `(...) {...}` for `name(...) {...}` (i.e. functions)
1362    ///
1363    /// Where `name` is added later (i.e. between `attrs` and `def_without_name`).
1364    pub def_without_name: pretty::Fragment,
1365}
1366
1367impl AttrsAndDef {
1368    /// Concat `attrs`, `name` and `def_without_name` into a [`pretty::Fragment`],
1369    /// effectively "filling in" the `name` missing from `def_without_name`.
1370    ///
1371    /// If `name` starts with an anchor definition, the definition of that anchor
1372    /// gets hoised to before (some non-empty) `attrs`, so that navigating to that
1373    /// anchor doesn't "hide" those attributes (requiring scrolling to see them).
1374    fn insert_name_before_def(self, name: impl Into<pretty::Fragment>) -> pretty::Fragment {
1375        let Self { attrs, def_without_name } = self;
1376
1377        let mut maybe_hoisted_anchor = pretty::Fragment::default();
1378        let mut maybe_def_start_anchor = pretty::Fragment::default();
1379        let mut maybe_def_end_anchor = pretty::Fragment::default();
1380        let mut name = name.into();
1381        if let [
1382            pretty::Node::Anchor { is_def: ref mut original_anchor_is_def @ true, anchor, text: _ },
1383            ..,
1384        ] = &mut name.nodes[..]
1385        {
1386            if !attrs.nodes.is_empty() {
1387                *original_anchor_is_def = false;
1388                maybe_hoisted_anchor = pretty::Node::Anchor {
1389                    is_def: true,
1390                    anchor: anchor.clone(),
1391                    text: vec![].into(),
1392                }
1393                .into();
1394            }
1395
1396            // HACK(eddyb) add a pair of anchors "bracketing" the definition
1397            // (though see below for why only the "start" side is currently
1398            // in use), to help with `multiversion` alignment, as long as
1399            // there's no alignment anchor already starting the definition.
1400            let has_alignment_anchor = match &def_without_name.nodes[..] {
1401                [pretty::Node::Anchor { is_def: true, anchor, text }, ..] => {
1402                    anchor.contains(Use::ANCHOR_ALIGNMENT_NAME_PREFIX) && text.is_empty()
1403                }
1404
1405                _ => false,
1406            };
1407            let mk_anchor_def = |suffix| {
1408                pretty::Node::Anchor {
1409                    is_def: true,
1410                    anchor: format!("{anchor}.{suffix}").into(),
1411                    text: vec![].into(),
1412                }
1413                .into()
1414            };
1415            if !has_alignment_anchor {
1416                maybe_def_start_anchor = mk_anchor_def("start");
1417                // FIXME(eddyb) having end alignment may be useful, but the
1418                // current logic in `multiversion` would prefer aligning
1419                // the ends, to the detriment of the rest (causing huge gaps).
1420                if false {
1421                    maybe_def_end_anchor = mk_anchor_def("end");
1422                }
1423            }
1424        }
1425        pretty::Fragment::new([
1426            maybe_hoisted_anchor,
1427            attrs,
1428            name,
1429            maybe_def_start_anchor,
1430            def_without_name,
1431            maybe_def_end_anchor,
1432        ])
1433    }
1434}
1435
1436pub trait Print {
1437    // FIXME(eddyb) maybe remove `type Output;` flexibility by having two traits
1438    // instead of one? (a method that returns `self.attrs` would allow for some
1439    // automation, and remove a lot of the noise that `AttrsAndDef` adds).
1440    type Output;
1441    fn print(&self, printer: &Printer<'_>) -> Self::Output;
1442}
1443
1444impl Use {
1445    /// Common implementation for [`Use::print`] and [`Use::print_as_def`].
1446    fn print_as_ref_or_def(&self, printer: &Printer<'_>, is_def: bool) -> pretty::Fragment {
1447        let style = printer.use_styles.get(self).unwrap_or(&UseStyle::Inline);
1448        match style {
1449            &UseStyle::Anon { parent_func, idx: _ } | &UseStyle::Named { parent_func, name: _ } => {
1450                // FIXME(eddyb) should this be used as part of `UseStyle`'s definition?
1451                #[derive(Debug, PartialEq, Eq)]
1452                enum Suffix<'a> {
1453                    Num(usize),
1454                    Name(&'a str),
1455                }
1456
1457                impl Suffix<'_> {
1458                    /// Format `self` into `w`, minimally escaping (`Sufix::Name`)
1459                    /// `char`s as `&#...;` HTML entities, to limit the charset
1460                    /// to `[A-Za-z0-9_]` (plus `[&#;]`, for escapes alone).
1461                    fn write_escaped_to(&self, w: &mut impl fmt::Write) -> fmt::Result {
1462                        match *self {
1463                            Suffix::Num(idx) => write!(w, "{idx}"),
1464                            Suffix::Name(mut name) => {
1465                                // HACK(eddyb) clearly separate from whatever is
1466                                // before (e.g. a category name), and disambiguate
1467                                // between e.g. `Num(123)` and `Name("123")`.
1468                                w.write_str("_")?;
1469
1470                                while !name.is_empty() {
1471                                    // HACK(eddyb) this is convenient way to
1472                                    // grab the longest prefix that is all valid.
1473                                    let is_valid = |c: char| c.is_ascii_alphanumeric() || c == '_';
1474                                    let name_after_valid = name.trim_start_matches(is_valid);
1475                                    let valid_prefix = &name[..name.len() - name_after_valid.len()];
1476                                    name = name_after_valid;
1477
1478                                    if !valid_prefix.is_empty() {
1479                                        w.write_str(valid_prefix)?;
1480                                    }
1481
1482                                    // `name` is either empty now, or starts with
1483                                    // an invalid `char` (that we need to escape).
1484                                    let mut chars = name.chars();
1485                                    if let Some(c) = chars.next() {
1486                                        assert!(!is_valid(c));
1487                                        write!(w, "&#{};", c as u32)?;
1488                                    }
1489                                    name = chars.as_str();
1490                                }
1491                                Ok(())
1492                            }
1493                        }
1494                    }
1495                }
1496
1497                let suffix_of = |style| match style {
1498                    &UseStyle::Anon { idx, .. } => Suffix::Num(idx),
1499                    UseStyle::Named { name, .. } => Suffix::Name(name),
1500                    UseStyle::Inline => unreachable!(),
1501                };
1502
1503                let (keyword, name_prefix) = self.keyword_and_name_prefix();
1504                let suffix = suffix_of(style);
1505
1506                // FIXME(eddyb) could the `anchor: Rc<str>` be cached?
1507                let mk_anchor = |anchor: String, text: Vec<_>| pretty::Node::Anchor {
1508                    is_def,
1509                    anchor: anchor.into(),
1510                    text: text.into(),
1511                };
1512
1513                // HACK(eddyb) these are "global" to the whole print `Plan`.
1514                if let Use::Node(Node::ModuleDialect | Node::ModuleDebugInfo) = self {
1515                    assert_eq!((is_def, name_prefix, suffix), (true, "", Suffix::Num(0)));
1516                    return mk_anchor(keyword.into(), vec![(None, keyword.into())]).into();
1517                }
1518
1519                let mut anchor = String::new();
1520                if let Some(func) = parent_func {
1521                    // Disambiguate intra-function anchors (labels/values) by
1522                    // prepending a prefix of the form `F123.`.
1523                    let func = Use::Node(Node::Func(func));
1524                    write!(anchor, "{}", func.keyword_and_name_prefix().1).unwrap();
1525                    suffix_of(&printer.use_styles[&func]).write_escaped_to(&mut anchor).unwrap();
1526                    anchor += ".";
1527                }
1528                anchor += name_prefix;
1529                suffix.write_escaped_to(&mut anchor).unwrap();
1530
1531                let name = if let Self::AlignmentAnchorForControlRegion(_)
1532                | Self::AlignmentAnchorForControlNode(_)
1533                | Self::AlignmentAnchorForDataInst(_) = self
1534                {
1535                    vec![]
1536                } else {
1537                    // HACK(eddyb) make the suffix larger for e.g. `T` than `v`.
1538                    let suffix_size = if name_prefix.ends_with(|c: char| c.is_ascii_uppercase()) {
1539                        -1
1540                    } else {
1541                        -2
1542                    };
1543                    let suffix = match suffix {
1544                        Suffix::Num(idx) => (
1545                            Some(pretty::Styles {
1546                                size: Some(suffix_size),
1547                                subscript: true,
1548                                ..Default::default()
1549                            }),
1550                            format!("{idx}").into(),
1551                        ),
1552                        Suffix::Name(name) => (
1553                            Some(pretty::Styles {
1554                                thickness: Some(0),
1555                                size: Some(suffix_size - 1),
1556                                color: Some(pretty::palettes::simple::LIGHT_GRAY),
1557                                ..Default::default()
1558                            }),
1559                            format!("`{name}`").into(),
1560                        ),
1561                    };
1562                    Some(keyword)
1563                        .filter(|kw| is_def && !kw.is_empty())
1564                        .into_iter()
1565                        .flat_map(|kw| [(None, kw.into()), (None, " ".into())])
1566                        .chain([(None, name_prefix.into()), suffix])
1567                        .collect()
1568                };
1569                mk_anchor(anchor, name).into()
1570            }
1571            UseStyle::Inline => match *self {
1572                Self::CxInterned(interned) => {
1573                    interned.print(printer).insert_name_before_def(pretty::Fragment::default())
1574                }
1575                Self::Node(node) => printer
1576                    .error_style()
1577                    .apply(format!(
1578                        "/* undefined {} */_",
1579                        node.keyword_and_name_prefix().map_or_else(|s| s, |(s, _)| s)
1580                    ))
1581                    .into(),
1582                Self::ControlRegionLabel(_)
1583                | Self::ControlRegionInput { .. }
1584                | Self::ControlNodeOutput { .. }
1585                | Self::DataInstOutput(_) => "_".into(),
1586
1587                Self::AlignmentAnchorForControlRegion(_)
1588                | Self::AlignmentAnchorForControlNode(_)
1589                | Self::AlignmentAnchorForDataInst(_) => unreachable!(),
1590            },
1591        }
1592    }
1593
1594    fn print_as_def(&self, printer: &Printer<'_>) -> pretty::Fragment {
1595        self.print_as_ref_or_def(printer, true)
1596    }
1597}
1598
1599impl Print for Use {
1600    type Output = pretty::Fragment;
1601    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
1602        self.print_as_ref_or_def(printer, false)
1603    }
1604}
1605
1606// Interned/module-stored nodes dispatch through the `Use` impl above.
1607impl Print for Type {
1608    type Output = pretty::Fragment;
1609    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
1610        Use::CxInterned(CxInterned::Type(*self)).print(printer)
1611    }
1612}
1613impl Print for Const {
1614    type Output = pretty::Fragment;
1615    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
1616        Use::CxInterned(CxInterned::Const(*self)).print(printer)
1617    }
1618}
1619impl Print for GlobalVar {
1620    type Output = pretty::Fragment;
1621    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
1622        Use::Node(Node::GlobalVar(*self)).print(printer)
1623    }
1624}
1625impl Print for Func {
1626    type Output = pretty::Fragment;
1627    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
1628        Use::Node(Node::Func(*self)).print(printer)
1629    }
1630}
1631
1632// NOTE(eddyb) the `Print` impl for `Node` is for the top-level definition,
1633// *not* any uses (which go through the `Print` impls above).
1634
1635impl Print for Plan<'_> {
1636    type Output = Versions<pretty::Fragment>;
1637    fn print(&self, printer: &Printer<'_>) -> Versions<pretty::Fragment> {
1638        self.print_all_nodes_and_or_root(printer, true, true)
1639    }
1640}
1641
1642impl Plan<'_> {
1643    fn print_all_nodes_and_or_root(
1644        &self,
1645        printer: &Printer<'_>,
1646        print_all_nodes: bool,
1647        print_root: bool,
1648    ) -> Versions<pretty::Fragment> {
1649        enum NodeOrRoot {
1650            Node(Node),
1651            Root,
1652        }
1653
1654        let all_nodes = printer
1655            .use_styles
1656            .keys()
1657            .filter_map(|&use_kind| match use_kind {
1658                Use::Node(node) => Some(node),
1659                _ => None,
1660            })
1661            .map(NodeOrRoot::Node);
1662        let root = [NodeOrRoot::Root].into_iter();
1663        let all_nodes_and_or_root = Some(all_nodes)
1664            .filter(|_| print_all_nodes)
1665            .into_iter()
1666            .flatten()
1667            .chain(Some(root).filter(|_| print_root).into_iter().flatten());
1668
1669        let per_node_versions_with_repeat_count =
1670            all_nodes_and_or_root.map(|node_or_root| -> SmallVec<[_; 1]> {
1671                // Only print `Node::AllCxInterned` once (it doesn't really have
1672                // per-version node definitions in the first place, anyway).
1673                if let NodeOrRoot::Node(node @ Node::AllCxInterned) = node_or_root {
1674                    node.keyword_and_name_prefix().unwrap_err();
1675
1676                    return [(CxInterned::print_all(printer), self.versions.len())]
1677                        .into_iter()
1678                        .collect();
1679                }
1680
1681                self.versions
1682                    .iter()
1683                    .map(move |version| match node_or_root {
1684                        NodeOrRoot::Node(node) => version
1685                            .node_defs
1686                            .get(&node)
1687                            .map(|def| {
1688                                def.print(printer)
1689                                    .insert_name_before_def(Use::Node(node).print_as_def(printer))
1690                            })
1691                            .unwrap_or_default(),
1692                        NodeOrRoot::Root => version.root.print(printer),
1693                    })
1694                    .dedup_with_count()
1695                    .map(|(repeat_count, fragment)| {
1696                        // FIXME(eddyb) consider rewriting intra-func anchors
1697                        // here, post-deduplication, to be unique per-version,
1698                        // though `multiversion` should probably handle it.
1699
1700                        (fragment, repeat_count)
1701                    })
1702                    .collect()
1703            });
1704
1705        // Unversioned, flatten the nodes.
1706        if self.versions.len() == 1 && self.versions[0].name.is_empty() {
1707            Versions::Single(pretty::Fragment::new(
1708                per_node_versions_with_repeat_count
1709                    .map(|mut versions_with_repeat_count| {
1710                        versions_with_repeat_count.pop().unwrap().0
1711                    })
1712                    .filter(|fragment| !fragment.nodes.is_empty())
1713                    .intersperse({
1714                        // Separate top-level definitions with empty lines.
1715                        // FIXME(eddyb) have an explicit `pretty::Node`
1716                        // for "vertical gap" instead.
1717                        "\n\n".into()
1718                    }),
1719            ))
1720        } else {
1721            Versions::Multiple {
1722                version_names: self.versions.iter().map(|v| v.name.clone()).collect(),
1723                per_row_versions_with_repeat_count: per_node_versions_with_repeat_count.collect(),
1724            }
1725        }
1726    }
1727}
1728
1729impl Print for Module {
1730    type Output = pretty::Fragment;
1731    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
1732        if self.exports.is_empty() {
1733            return pretty::Fragment::default();
1734        }
1735
1736        pretty::Fragment::new([
1737            printer.declarative_keyword_style().apply("export").into(),
1738            " ".into(),
1739            pretty::join_comma_sep(
1740                "{",
1741                self.exports
1742                    .iter()
1743                    .map(|(export_key, exportee)| {
1744                        pretty::Fragment::new([
1745                            export_key.print(printer),
1746                            ": ".into(),
1747                            exportee.print(printer),
1748                        ])
1749                    })
1750                    .map(|entry| {
1751                        pretty::Fragment::new([pretty::Node::ForceLineSeparation.into(), entry])
1752                    }),
1753                "}",
1754            ),
1755        ])
1756    }
1757}
1758
1759impl Print for NodeDef<'_> {
1760    type Output = AttrsAndDef;
1761    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
1762        match self {
1763            Self::ModuleDialect(dialect) => dialect.print(printer),
1764            Self::ModuleDebugInfo(debug_info) => debug_info.print(printer),
1765            Self::GlobalVar(gv_decl) => gv_decl.print(printer),
1766            Self::Func(func_decl) => func_decl.print(printer),
1767        }
1768    }
1769}
1770
1771impl Print for ModuleDialect {
1772    type Output = AttrsAndDef;
1773    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
1774        let dialect = match self {
1775            Self::Spv(dialect) => dialect.print(printer),
1776        };
1777
1778        AttrsAndDef {
1779            attrs: pretty::Fragment::default(),
1780            def_without_name: pretty::Fragment::new([" = ".into(), dialect]),
1781        }
1782    }
1783}
1784impl Print for spv::Dialect {
1785    type Output = pretty::Fragment;
1786    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
1787        let Self {
1788            version_major,
1789            version_minor,
1790            capabilities,
1791            extensions,
1792            addressing_model,
1793            memory_model,
1794        } = self;
1795
1796        let wk = &spv::spec::Spec::get().well_known;
1797        pretty::Fragment::new([
1798            printer
1799                .demote_style_for_namespace_prefix(printer.spv_base_style())
1800                .apply("spv.")
1801                .into(),
1802            printer.spv_base_style().apply("Module").into(),
1803            pretty::join_comma_sep(
1804                "(",
1805                [pretty::Fragment::new([
1806                    printer.pretty_named_argument_prefix("version"),
1807                    printer.numeric_literal_style().apply(format!("{version_major}")).into(),
1808                    ".".into(),
1809                    printer.numeric_literal_style().apply(format!("{version_minor}")).into(),
1810                ])]
1811                .into_iter()
1812                .chain((!extensions.is_empty()).then(|| {
1813                    pretty::Fragment::new([
1814                        printer.pretty_named_argument_prefix("extensions"),
1815                        pretty::join_comma_sep(
1816                            "{",
1817                            extensions.iter().map(|ext| printer.pretty_string_literal(ext)),
1818                            "}",
1819                        ),
1820                    ])
1821                }))
1822                .chain(
1823                    // FIXME(eddyb) consider a `spv.Capability.{A,B,C}` style.
1824                    (!capabilities.is_empty()).then(|| {
1825                        let cap_imms = |cap| [spv::Imm::Short(wk.Capability, cap)];
1826
1827                        // HACK(eddyb) construct a custom `spv.Capability.{A,B,C}`.
1828                        let capability_namespace_prefix = printer
1829                            .pretty_spv_print_tokens_for_operand({
1830                                let mut tokens = spv::print::operand_from_imms(cap_imms(0));
1831                                assert!(matches!(
1832                                    tokens.tokens.pop(),
1833                                    Some(spv::print::Token::EnumerandName(_))
1834                                ));
1835                                tokens
1836                            });
1837
1838                        let mut cap_names = capabilities.iter().map(|&cap| {
1839                            printer.pretty_spv_print_tokens_for_operand({
1840                                let mut tokens = spv::print::operand_from_imms(cap_imms(cap));
1841                                tokens.tokens.drain(..tokens.tokens.len() - 1);
1842                                assert!(matches!(tokens.tokens[..], [
1843                                    spv::print::Token::EnumerandName(_)
1844                                ]));
1845                                tokens
1846                            })
1847                        });
1848
1849                        pretty::Fragment::new([
1850                            capability_namespace_prefix,
1851                            if cap_names.len() == 1 {
1852                                cap_names.next().unwrap()
1853                            } else {
1854                                pretty::join_comma_sep("{", cap_names, "}")
1855                            },
1856                        ])
1857                    }),
1858                )
1859                .chain(
1860                    (*addressing_model != wk.Logical)
1861                        .then(|| printer.pretty_spv_imm(wk.AddressingModel, *addressing_model)),
1862                )
1863                .chain([printer.pretty_spv_imm(wk.MemoryModel, *memory_model)]),
1864                ")",
1865            ),
1866        ])
1867    }
1868}
1869
1870impl Print for ModuleDebugInfo {
1871    type Output = AttrsAndDef;
1872    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
1873        let debug_info = match self {
1874            Self::Spv(debug_info) => debug_info.print(printer),
1875        };
1876
1877        AttrsAndDef {
1878            attrs: pretty::Fragment::default(),
1879            def_without_name: pretty::Fragment::new([" = ".into(), debug_info]),
1880        }
1881    }
1882}
1883
1884impl Print for spv::ModuleDebugInfo {
1885    type Output = pretty::Fragment;
1886    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
1887        let Self {
1888            original_generator_magic,
1889            source_languages,
1890            source_extensions,
1891            module_processes,
1892        } = self;
1893
1894        let wk = &spv::spec::Spec::get().well_known;
1895        pretty::Fragment::new([
1896            printer
1897                .demote_style_for_namespace_prefix(
1898                    printer.demote_style_for_namespace_prefix(printer.spv_base_style()),
1899                )
1900                .apply("spv.")
1901                .into(),
1902            printer
1903                .demote_style_for_namespace_prefix(printer.spv_base_style())
1904                .apply("Module.")
1905                .into(),
1906            printer.spv_base_style().apply("DebugInfo").into(),
1907            pretty::join_comma_sep(
1908                "(",
1909                [
1910                    original_generator_magic.map(|generator_magic| {
1911                        let (tool_id, tool_version) =
1912                            (generator_magic.get() >> 16, generator_magic.get() as u16);
1913                        pretty::Fragment::new([
1914                            printer.pretty_named_argument_prefix("generator"),
1915                            printer
1916                                .demote_style_for_namespace_prefix(printer.spv_base_style())
1917                                .apply("spv.")
1918                                .into(),
1919                            printer.spv_base_style().apply("Tool").into(),
1920                            pretty::join_comma_sep(
1921                                "(",
1922                                [
1923                                    Some(pretty::Fragment::new([
1924                                        printer.pretty_named_argument_prefix("id"),
1925                                        printer
1926                                            .numeric_literal_style()
1927                                            .apply(format!("{tool_id}"))
1928                                            .into(),
1929                                    ])),
1930                                    (tool_version != 0).then(|| {
1931                                        pretty::Fragment::new([
1932                                            printer.pretty_named_argument_prefix("version"),
1933                                            printer
1934                                                .numeric_literal_style()
1935                                                .apply(format!("{tool_version}"))
1936                                                .into(),
1937                                        ])
1938                                    }),
1939                                ]
1940                                .into_iter()
1941                                .flatten(),
1942                                ")",
1943                            ),
1944                        ])
1945                    }),
1946                    (!source_languages.is_empty()).then(|| {
1947                        pretty::Fragment::new([
1948                            printer.pretty_named_argument_prefix("source_languages"),
1949                            pretty::join_comma_sep(
1950                                "{",
1951                                source_languages
1952                                    .iter()
1953                                    .map(|(lang, sources)| {
1954                                        let spv::DebugSources { file_contents } = sources;
1955                                        pretty::Fragment::new([
1956                                            printer.pretty_spv_imm(wk.SourceLanguage, lang.lang),
1957                                            "(".into(),
1958                                            printer.pretty_named_argument_prefix("version"),
1959                                            printer
1960                                                .numeric_literal_style()
1961                                                .apply(format!("{}", lang.version))
1962                                                .into(),
1963                                            "): ".into(),
1964                                            pretty::join_comma_sep(
1965                                                "{",
1966                                                file_contents
1967                                                    .iter()
1968                                                    .map(|(&file, contents)| {
1969                                                        pretty::Fragment::new([
1970                                                            printer.pretty_string_literal(
1971                                                                &printer.cx[file],
1972                                                            ),
1973                                                            pretty::join_space(":", [printer
1974                                                                .pretty_string_literal(contents)]),
1975                                                        ])
1976                                                    })
1977                                                    .map(|entry| {
1978                                                        pretty::Fragment::new([
1979                                                            pretty::Node::ForceLineSeparation
1980                                                                .into(),
1981                                                            entry,
1982                                                        ])
1983                                                    }),
1984                                                "}",
1985                                            ),
1986                                        ])
1987                                    })
1988                                    .map(|entry| {
1989                                        pretty::Fragment::new([
1990                                            pretty::Node::ForceLineSeparation.into(),
1991                                            entry,
1992                                        ])
1993                                    }),
1994                                "}",
1995                            ),
1996                        ])
1997                    }),
1998                    (!source_extensions.is_empty()).then(|| {
1999                        pretty::Fragment::new([
2000                            printer.pretty_named_argument_prefix("source_extensions"),
2001                            pretty::join_comma_sep(
2002                                "[",
2003                                source_extensions
2004                                    .iter()
2005                                    .map(|ext| printer.pretty_string_literal(ext)),
2006                                "]",
2007                            ),
2008                        ])
2009                    }),
2010                    (!module_processes.is_empty()).then(|| {
2011                        pretty::Fragment::new([
2012                            printer.pretty_named_argument_prefix("module_processes"),
2013                            pretty::join_comma_sep(
2014                                "[",
2015                                module_processes
2016                                    .iter()
2017                                    .map(|proc| printer.pretty_string_literal(proc)),
2018                                "]",
2019                            ),
2020                        ])
2021                    }),
2022                ]
2023                .into_iter()
2024                .flatten(),
2025                ")",
2026            ),
2027        ])
2028    }
2029}
2030
2031impl Print for ExportKey {
2032    type Output = pretty::Fragment;
2033    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2034        match self {
2035            &Self::LinkName(name) => printer.pretty_string_literal(&printer.cx[name]),
2036
2037            // HACK(eddyb) `interface_global_vars` should be recomputed by
2038            // `spv::lift` anyway, so hiding them here mimics that.
2039            Self::SpvEntryPoint { imms, interface_global_vars: _ } => {
2040                let wk = &spv::spec::Spec::get().well_known;
2041
2042                printer.pretty_spv_inst(printer.spv_op_style(), wk.OpEntryPoint, imms, [None])
2043            }
2044        }
2045    }
2046}
2047
2048impl Print for Exportee {
2049    type Output = pretty::Fragment;
2050    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2051        match *self {
2052            Self::GlobalVar(gv) => gv.print(printer),
2053            Self::Func(func) => func.print(printer),
2054        }
2055    }
2056}
2057
2058impl CxInterned {
2059    fn print_all(printer: &Printer<'_>) -> pretty::Fragment {
2060        let fragments = printer
2061            .use_styles
2062            .iter()
2063            .filter_map(|(&use_kind, use_style)| match (use_kind, use_style) {
2064                (Use::CxInterned(interned), UseStyle::Anon { .. } | UseStyle::Named { .. }) => {
2065                    Some(interned)
2066                }
2067                _ => None,
2068            })
2069            .map(|interned| {
2070                interned.print(printer).insert_name_before_def(pretty::Fragment::new([
2071                    Use::CxInterned(interned).print_as_def(printer),
2072                    " = ".into(),
2073                ]))
2074            })
2075            .intersperse({
2076                // Separate top-level definitions with empty lines.
2077                // FIXME(eddyb) have an explicit `pretty::Node`
2078                // for "vertical gap" instead.
2079                "\n\n".into()
2080            });
2081
2082        pretty::Fragment::new(fragments)
2083    }
2084}
2085
2086impl Print for CxInterned {
2087    type Output = AttrsAndDef;
2088    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
2089        match *self {
2090            Self::Type(ty) => printer.cx[ty].print(printer),
2091            Self::Const(ct) => printer.cx[ct].print(printer),
2092        }
2093    }
2094}
2095
2096impl Print for AttrSet {
2097    type Output = pretty::Fragment;
2098    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2099        let AttrSetDef { attrs } = &printer.cx[*self];
2100
2101        // Avoid showing `#[spv.OpName("...")]` when it's already in use as the
2102        // name of the definition (but keep it in all other cases).
2103        let spv_name_to_hide = printer.attrs_with_spv_name_in_use.get(self).copied();
2104
2105        pretty::Fragment::new(
2106            attrs
2107                .iter()
2108                .filter(|attr| match attr {
2109                    Attr::SpvAnnotation(spv_inst) => Some(spv_inst) != spv_name_to_hide,
2110                    _ => true,
2111                })
2112                .map(|attr| attr.print(printer))
2113                .flat_map(|entry| [entry, pretty::Node::ForceLineSeparation.into()]),
2114        )
2115    }
2116}
2117
2118impl Print for Attr {
2119    type Output = pretty::Fragment;
2120    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2121        let non_comment_attr = match self {
2122            Attr::Diagnostics(diags) => {
2123                return pretty::Fragment::new(
2124                    diags
2125                        .0
2126                        .iter()
2127                        .map(|diag| {
2128                            let Diag { level, message } = diag;
2129
2130                            // FIXME(eddyb) the plan was to use 💥/⮿/⚠
2131                            // for bug/error/warning, but it doesn't really
2132                            // render correctly, so allcaps it is for now.
2133                            let (icon, icon_color) = match level {
2134                                DiagLevel::Bug(_) => ("BUG", pretty::palettes::simple::MAGENTA),
2135                                DiagLevel::Error => ("ERR", pretty::palettes::simple::RED),
2136                                DiagLevel::Warning => ("WARN", pretty::palettes::simple::YELLOW),
2137                            };
2138
2139                            let grayish =
2140                                |[r, g, b]: [u8; 3]| [(r / 2) + 64, (g / 2) + 64, (b / 2) + 64];
2141                            let comment_style = pretty::Styles::color(grayish(icon_color));
2142
2143                            // FIXME(eddyb) maybe make this a link to the source code?
2144                            let bug_location_prefix = match level {
2145                                DiagLevel::Bug(location) => {
2146                                    let location = location.to_string();
2147                                    let location = match location.rsplit_once("/src/") {
2148                                        Some((_path_prefix, intra_src)) => intra_src,
2149                                        None => &location,
2150                                    };
2151                                    comment_style.apply(format!("[{location}] ")).into()
2152                                }
2153                                DiagLevel::Error | DiagLevel::Warning => {
2154                                    pretty::Fragment::default()
2155                                }
2156                            };
2157
2158                            let mut printed_message = message.print(printer);
2159
2160                            // HACK(eddyb) apply the right style to all the plain
2161                            // text parts of the already-printed message.
2162                            // FIXME(eddyb) consider merging the styles somewhat?
2163                            for node in &mut printed_message.nodes {
2164                                if let pretty::Node::Text(style @ None, _) = node {
2165                                    *style = Some(comment_style);
2166                                }
2167                            }
2168
2169                            // HACK(eddyb) this would ideally use line comments,
2170                            // but adding the line prefix properly to everything
2171                            // is a bit of a pain without special `pretty` support.
2172                            pretty::Fragment::new([
2173                                comment_style.apply("/*"),
2174                                pretty::Node::BreakingOnlySpace,
2175                                pretty::Node::InlineOrIndentedBlock(vec![pretty::Fragment::new([
2176                                    pretty::Styles {
2177                                        thickness: Some(3),
2178
2179                                        // HACK(eddyb) this allows larger "icons"
2180                                        // without adding gaps via `line-height`.
2181                                        subscript: true,
2182                                        size: Some(2),
2183
2184                                        ..pretty::Styles::color(icon_color)
2185                                    }
2186                                    .apply(icon)
2187                                    .into(),
2188                                    " ".into(),
2189                                    bug_location_prefix,
2190                                    printed_message,
2191                                ])]),
2192                                pretty::Node::BreakingOnlySpace,
2193                                comment_style.apply("*/"),
2194                            ])
2195                        })
2196                        .intersperse(pretty::Node::ForceLineSeparation.into()),
2197                );
2198            }
2199
2200            Attr::QPtr(attr) => {
2201                let (name, params_inputs) = match attr {
2202                    QPtrAttr::ToSpvPtrInput { input_idx, pointee } => (
2203                        "to_spv_ptr_input",
2204                        pretty::Fragment::new([pretty::join_comma_sep(
2205                            "(",
2206                            [
2207                                pretty::Fragment::new([
2208                                    printer.pretty_named_argument_prefix("input_idx"),
2209                                    printer
2210                                        .numeric_literal_style()
2211                                        .apply(format!("{input_idx}"))
2212                                        .into(),
2213                                ]),
2214                                pointee.0.print(printer),
2215                            ],
2216                            ")",
2217                        )]),
2218                    ),
2219
2220                    QPtrAttr::FromSpvPtrOutput { addr_space, pointee } => (
2221                        "from_spv_ptr_output",
2222                        pretty::join_comma_sep(
2223                            "(",
2224                            [addr_space.0.print(printer), pointee.0.print(printer)],
2225                            ")",
2226                        ),
2227                    ),
2228
2229                    QPtrAttr::Usage(usage) => {
2230                        ("usage", pretty::join_comma_sep("(", [usage.0.print(printer)], ")"))
2231                    }
2232                };
2233                pretty::Fragment::new([
2234                    printer
2235                        .demote_style_for_namespace_prefix(printer.attr_style())
2236                        .apply("qptr.")
2237                        .into(),
2238                    printer.attr_style().apply(name).into(),
2239                    params_inputs,
2240                ])
2241            }
2242
2243            Attr::SpvAnnotation(spv::Inst { opcode, imms }) => {
2244                let wk = &spv::spec::Spec::get().well_known;
2245
2246                // HACK(eddyb) `#[spv.OpDecorate(...)]` is redundant (with its operand).
2247                if [wk.OpDecorate, wk.OpDecorateString, wk.OpExecutionMode].contains(opcode) {
2248                    printer.pretty_spv_operand_from_imms(imms.iter().copied())
2249                } else if *opcode == wk.OpName {
2250                    // HACK(eddyb) unlike `OpDecorate`, we can't just omit `OpName`,
2251                    // but pretending it's a SPIR-T-specific `#[name = "..."]`
2252                    // attribute should be good enough for now.
2253                    pretty::Fragment::new([
2254                        printer.attr_style().apply("name = ").into(),
2255                        printer.pretty_spv_operand_from_imms(imms.iter().copied()),
2256                    ])
2257                } else {
2258                    printer.pretty_spv_inst(printer.attr_style(), *opcode, imms, [None])
2259                }
2260            }
2261            &Attr::SpvDebugLine { file_path, line, col } => {
2262                // HACK(eddyb) Rust-GPU's column numbers seem
2263                // off-by-one wrt what e.g. VSCode expects
2264                // for `:line:col` syntax, but it's hard to
2265                // tell from the spec and `glslang` doesn't
2266                // even emit column numbers at all!
2267                let col = col + 1;
2268
2269                // HACK(eddyb) only use skip string quoting
2270                // and escaping for well-behaved file paths.
2271                let file_path = &printer.cx[file_path.0];
2272                let comment = if file_path.chars().all(|c| c.is_ascii_graphic() && c != ':') {
2273                    format!("// at {file_path}:{line}:{col}")
2274                } else {
2275                    format!("// at {file_path:?}:{line}:{col}")
2276                };
2277                return printer.comment_style().apply(comment).into();
2278            }
2279            &Attr::SpvBitflagsOperand(imm) => printer.pretty_spv_operand_from_imms([imm]),
2280        };
2281        pretty::Fragment::new([
2282            printer.attr_style().apply("#[").into(),
2283            non_comment_attr,
2284            printer.attr_style().apply("]").into(),
2285        ])
2286    }
2287}
2288
2289impl Print for Vec<DiagMsgPart> {
2290    type Output = pretty::Fragment;
2291    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2292        pretty::Fragment::new(self.iter().map(|part| match part {
2293            DiagMsgPart::Plain(text) => pretty::Node::Text(None, text.clone()).into(),
2294            DiagMsgPart::Attrs(attrs) => attrs.print(printer),
2295            DiagMsgPart::Type(ty) => ty.print(printer),
2296            DiagMsgPart::Const(ct) => ct.print(printer),
2297            DiagMsgPart::QPtrUsage(usage) => usage.print(printer),
2298        }))
2299    }
2300}
2301
2302impl Print for QPtrUsage {
2303    type Output = pretty::Fragment;
2304    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2305        match self {
2306            QPtrUsage::Handles(qptr::shapes::Handle::Opaque(ty)) => ty.print(printer),
2307            QPtrUsage::Handles(qptr::shapes::Handle::Buffer(_, data_usage)) => {
2308                pretty::Fragment::new([
2309                    printer.declarative_keyword_style().apply("buffer_data").into(),
2310                    pretty::join_comma_sep("(", [data_usage.print(printer)], ")"),
2311                ])
2312            }
2313            QPtrUsage::Memory(usage) => usage.print(printer),
2314        }
2315    }
2316}
2317
2318impl Print for QPtrMemUsage {
2319    type Output = pretty::Fragment;
2320    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2321        // FIXME(eddyb) should this be a helper on `Printer`?
2322        let num_lit = |x: u32| printer.numeric_literal_style().apply(format!("{x}")).into();
2323
2324        match &self.kind {
2325            QPtrMemUsageKind::Unused => "_".into(),
2326            // FIXME(eddyb) should the distinction be noted?
2327            &QPtrMemUsageKind::StrictlyTyped(ty) | &QPtrMemUsageKind::DirectAccess(ty) => {
2328                ty.print(printer)
2329            }
2330            QPtrMemUsageKind::OffsetBase(entries) => pretty::join_comma_sep(
2331                "{",
2332                entries
2333                    .iter()
2334                    .map(|(&offset, sub_usage)| {
2335                        pretty::Fragment::new([
2336                            num_lit(offset),
2337                            "..".into(),
2338                            sub_usage
2339                                .max_size
2340                                .and_then(|max_size| offset.checked_add(max_size))
2341                                .map(num_lit)
2342                                .unwrap_or_default(),
2343                            " => ".into(),
2344                            sub_usage.print(printer),
2345                        ])
2346                    })
2347                    .map(|entry| {
2348                        pretty::Fragment::new([pretty::Node::ForceLineSeparation.into(), entry])
2349                    }),
2350                "}",
2351            ),
2352            QPtrMemUsageKind::DynOffsetBase { element, stride } => pretty::Fragment::new([
2353                "(".into(),
2354                num_lit(0),
2355                "..".into(),
2356                self.max_size
2357                    .map(|max_size| max_size / stride.get())
2358                    .map(num_lit)
2359                    .unwrap_or_default(),
2360                ") × ".into(),
2361                num_lit(stride.get()),
2362                " => ".into(),
2363                element.print(printer),
2364            ]),
2365        }
2366    }
2367}
2368
2369impl Print for TypeDef {
2370    type Output = AttrsAndDef;
2371    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
2372        let Self { attrs, kind } = self;
2373
2374        let wk = &spv::spec::Spec::get().well_known;
2375
2376        // FIXME(eddyb) should this be done by lowering SPIR-V types to SPIR-T?
2377        let kw = |kw| printer.declarative_keyword_style().apply(kw).into();
2378        let compact_def = if let &TypeKind::SpvInst {
2379            spv_inst: spv::Inst { opcode, ref imms },
2380            ref type_and_const_inputs,
2381        } = kind
2382        {
2383            if opcode == wk.OpTypeBool {
2384                Some(kw("bool".into()))
2385            } else if opcode == wk.OpTypeInt {
2386                let (width, signed) = match imms[..] {
2387                    [spv::Imm::Short(_, width), spv::Imm::Short(_, signedness)] => {
2388                        (width, signedness != 0)
2389                    }
2390                    _ => unreachable!(),
2391                };
2392
2393                Some(if signed { kw(format!("s{width}")) } else { kw(format!("u{width}")) })
2394            } else if opcode == wk.OpTypeFloat {
2395                let width = match imms[..] {
2396                    [spv::Imm::Short(_, width)] => width,
2397                    _ => unreachable!(),
2398                };
2399
2400                Some(kw(format!("f{width}")))
2401            } else if opcode == wk.OpTypeVector {
2402                let (elem_ty, elem_count) = match (&imms[..], &type_and_const_inputs[..]) {
2403                    (&[spv::Imm::Short(_, elem_count)], &[TypeOrConst::Type(elem_ty)]) => {
2404                        (elem_ty, elem_count)
2405                    }
2406                    _ => unreachable!(),
2407                };
2408
2409                Some(pretty::Fragment::new([
2410                    elem_ty.print(printer),
2411                    "×".into(),
2412                    printer.numeric_literal_style().apply(format!("{elem_count}")).into(),
2413                ]))
2414            } else {
2415                None
2416            }
2417        } else {
2418            None
2419        };
2420
2421        AttrsAndDef {
2422            attrs: attrs.print(printer),
2423            def_without_name: if let Some(def) = compact_def {
2424                def
2425            } else {
2426                match kind {
2427                    // FIXME(eddyb) should this be shortened to `qtr`?
2428                    TypeKind::QPtr => printer.declarative_keyword_style().apply("qptr").into(),
2429
2430                    TypeKind::SpvInst { spv_inst, type_and_const_inputs } => printer
2431                        .pretty_spv_inst(
2432                            printer.spv_op_style(),
2433                            spv_inst.opcode,
2434                            &spv_inst.imms,
2435                            type_and_const_inputs.iter().map(|&ty_or_ct| match ty_or_ct {
2436                                TypeOrConst::Type(ty) => ty.print(printer),
2437                                TypeOrConst::Const(ct) => ct.print(printer),
2438                            }),
2439                        ),
2440                    TypeKind::SpvStringLiteralForExtInst => pretty::Fragment::new([
2441                        printer.error_style().apply("type_of").into(),
2442                        "(".into(),
2443                        printer.pretty_spv_opcode(printer.spv_op_style(), wk.OpString),
2444                        ")".into(),
2445                    ]),
2446                }
2447            },
2448        }
2449    }
2450}
2451
2452impl Print for ConstDef {
2453    type Output = AttrsAndDef;
2454    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
2455        let Self { attrs, ty, kind } = self;
2456
2457        let wk = &spv::spec::Spec::get().well_known;
2458
2459        let kw = |kw| printer.declarative_keyword_style().apply(kw).into();
2460        let literal_ty_suffix = |ty| {
2461            pretty::Styles {
2462                // HACK(eddyb) the exact type detracts from the value.
2463                color_opacity: Some(0.4),
2464                subscript: true,
2465                ..printer.declarative_keyword_style()
2466            }
2467            .apply(ty)
2468        };
2469        let compact_def = if let ConstKind::SpvInst { spv_inst_and_const_inputs } = kind {
2470            let (spv_inst, _const_inputs) = &**spv_inst_and_const_inputs;
2471            let &spv::Inst { opcode, ref imms } = spv_inst;
2472
2473            if opcode == wk.OpConstantFalse {
2474                Some(kw("false"))
2475            } else if opcode == wk.OpConstantTrue {
2476                Some(kw("true"))
2477            } else if opcode == wk.OpConstant {
2478                // HACK(eddyb) it's simpler to only handle a limited subset of
2479                // integer/float bit-widths, for now.
2480                let raw_bits = match imms[..] {
2481                    [spv::Imm::Short(_, x)] => Some(u64::from(x)),
2482                    [spv::Imm::LongStart(_, lo), spv::Imm::LongCont(_, hi)] => {
2483                        Some(u64::from(lo) | (u64::from(hi) << 32))
2484                    }
2485                    _ => None,
2486                };
2487
2488                if let (
2489                    Some(raw_bits),
2490                    &TypeKind::SpvInst {
2491                        spv_inst: spv::Inst { opcode: ty_opcode, imms: ref ty_imms },
2492                        ..
2493                    },
2494                ) = (raw_bits, &printer.cx[*ty].kind)
2495                {
2496                    if ty_opcode == wk.OpTypeInt {
2497                        let (width, signed) = match ty_imms[..] {
2498                            [spv::Imm::Short(_, width), spv::Imm::Short(_, signedness)] => {
2499                                (width, signedness != 0)
2500                            }
2501                            _ => unreachable!(),
2502                        };
2503
2504                        if width <= 64 {
2505                            let (printed_value, ty) = if signed {
2506                                let sext_raw_bits =
2507                                    (raw_bits as u128 as i128) << (128 - width) >> (128 - width);
2508                                (format!("{sext_raw_bits}"), format!("s{width}"))
2509                            } else {
2510                                (format!("{raw_bits}"), format!("u{width}"))
2511                            };
2512                            Some(pretty::Fragment::new([
2513                                printer.numeric_literal_style().apply(printed_value),
2514                                literal_ty_suffix(ty),
2515                            ]))
2516                        } else {
2517                            None
2518                        }
2519                    } else if ty_opcode == wk.OpTypeFloat {
2520                        let width = match ty_imms[..] {
2521                            [spv::Imm::Short(_, width)] => width,
2522                            _ => unreachable!(),
2523                        };
2524
2525                        /// Check that parsing the result of printing produces
2526                        /// the original bits of the floating-point value, and
2527                        /// only return `Some` if that is the case.
2528                        fn bitwise_roundtrip_float_print<
2529                            BITS: Copy + PartialEq,
2530                            FLOAT: std::fmt::Debug + std::str::FromStr,
2531                        >(
2532                            bits: BITS,
2533                            float_from_bits: impl FnOnce(BITS) -> FLOAT,
2534                            float_to_bits: impl FnOnce(FLOAT) -> BITS,
2535                        ) -> Option<String> {
2536                            let float = float_from_bits(bits);
2537                            Some(format!("{float:?}")).filter(|s| {
2538                                s.parse::<FLOAT>()
2539                                    .map(float_to_bits)
2540                                    .map_or(false, |roundtrip_bits| roundtrip_bits == bits)
2541                            })
2542                        }
2543
2544                        let printed_value = match width {
2545                            32 => bitwise_roundtrip_float_print(
2546                                raw_bits as u32,
2547                                f32::from_bits,
2548                                f32::to_bits,
2549                            ),
2550                            64 => bitwise_roundtrip_float_print(
2551                                raw_bits,
2552                                f64::from_bits,
2553                                f64::to_bits,
2554                            ),
2555                            _ => None,
2556                        };
2557                        printed_value.map(|s| {
2558                            pretty::Fragment::new([
2559                                printer.numeric_literal_style().apply(s),
2560                                literal_ty_suffix(format!("f{width}")),
2561                            ])
2562                        })
2563                    } else {
2564                        None
2565                    }
2566                } else {
2567                    None
2568                }
2569            } else {
2570                None
2571            }
2572        } else {
2573            None
2574        };
2575
2576        AttrsAndDef {
2577            attrs: attrs.print(printer),
2578            def_without_name: compact_def.unwrap_or_else(|| match kind {
2579                &ConstKind::PtrToGlobalVar(gv) => {
2580                    pretty::Fragment::new(["&".into(), gv.print(printer)])
2581                }
2582                ConstKind::SpvInst { spv_inst_and_const_inputs } => {
2583                    let (spv_inst, const_inputs) = &**spv_inst_and_const_inputs;
2584                    pretty::Fragment::new([
2585                        printer.pretty_spv_inst(
2586                            printer.spv_op_style(),
2587                            spv_inst.opcode,
2588                            &spv_inst.imms,
2589                            const_inputs.iter().map(|ct| ct.print(printer)),
2590                        ),
2591                        printer.pretty_type_ascription_suffix(*ty),
2592                    ])
2593                }
2594                &ConstKind::SpvStringLiteralForExtInst(s) => pretty::Fragment::new([
2595                    printer.pretty_spv_opcode(printer.spv_op_style(), wk.OpString),
2596                    "(".into(),
2597                    printer.pretty_string_literal(&printer.cx[s]),
2598                    ")".into(),
2599                ]),
2600            }),
2601        }
2602    }
2603}
2604
2605impl Print for Import {
2606    type Output = pretty::Fragment;
2607    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2608        match self {
2609            &Self::LinkName(name) => pretty::Fragment::new([
2610                printer.declarative_keyword_style().apply("import").into(),
2611                " ".into(),
2612                printer.pretty_string_literal(&printer.cx[name]),
2613            ]),
2614        }
2615    }
2616}
2617
2618impl Print for GlobalVarDecl {
2619    type Output = AttrsAndDef;
2620    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
2621        let Self { attrs, type_of_ptr_to, shape, addr_space, def } = self;
2622
2623        let wk = &spv::spec::Spec::get().well_known;
2624
2625        // HACK(eddyb) get the pointee type from SPIR-V `OpTypePointer`, but
2626        // ideally the `GlobalVarDecl` would hold that type itself.
2627        let type_ascription_suffix = match &printer.cx[*type_of_ptr_to].kind {
2628            TypeKind::QPtr if shape.is_some() => match shape.unwrap() {
2629                qptr::shapes::GlobalVarShape::Handles { handle, fixed_count } => {
2630                    let handle = match handle {
2631                        qptr::shapes::Handle::Opaque(ty) => ty.print(printer),
2632                        qptr::shapes::Handle::Buffer(addr_space, buf) => pretty::Fragment::new([
2633                            printer.declarative_keyword_style().apply("buffer").into(),
2634                            pretty::join_comma_sep(
2635                                "(",
2636                                [
2637                                    addr_space.print(printer),
2638                                    pretty::Fragment::new([
2639                                        printer.pretty_named_argument_prefix("size"),
2640                                        pretty::Fragment::new(
2641                                            Some(buf.fixed_base.size)
2642                                                .filter(|&base_size| {
2643                                                    base_size > 0 || buf.dyn_unit_stride.is_none()
2644                                                })
2645                                                .map(|base_size| {
2646                                                    printer
2647                                                        .numeric_literal_style()
2648                                                        .apply(base_size.to_string())
2649                                                        .into()
2650                                                })
2651                                                .into_iter()
2652                                                .chain(buf.dyn_unit_stride.map(|stride| {
2653                                                    pretty::Fragment::new([
2654                                                        "N × ".into(),
2655                                                        printer
2656                                                            .numeric_literal_style()
2657                                                            .apply(stride.to_string()),
2658                                                    ])
2659                                                }))
2660                                                .intersperse_with(|| " + ".into()),
2661                                        ),
2662                                    ]),
2663                                    pretty::Fragment::new([
2664                                        printer.pretty_named_argument_prefix("align"),
2665                                        printer
2666                                            .numeric_literal_style()
2667                                            .apply(buf.fixed_base.align.to_string())
2668                                            .into(),
2669                                    ]),
2670                                ],
2671                                ")",
2672                            ),
2673                        ]),
2674                    };
2675
2676                    let handles = if fixed_count.map_or(0, |c| c.get()) == 1 {
2677                        handle
2678                    } else {
2679                        pretty::Fragment::new([
2680                            "[".into(),
2681                            fixed_count
2682                                .map(|count| {
2683                                    pretty::Fragment::new([
2684                                        printer.numeric_literal_style().apply(count.to_string()),
2685                                        " × ".into(),
2686                                    ])
2687                                })
2688                                .unwrap_or_default(),
2689                            handle,
2690                            "]".into(),
2691                        ])
2692                    };
2693                    pretty::join_space(":", [handles])
2694                }
2695                qptr::shapes::GlobalVarShape::UntypedData(mem_layout) => pretty::Fragment::new([
2696                    " ".into(),
2697                    printer.declarative_keyword_style().apply("layout").into(),
2698                    pretty::join_comma_sep(
2699                        "(",
2700                        [
2701                            pretty::Fragment::new([
2702                                printer.pretty_named_argument_prefix("size"),
2703                                printer
2704                                    .numeric_literal_style()
2705                                    .apply(mem_layout.size.to_string())
2706                                    .into(),
2707                            ]),
2708                            pretty::Fragment::new([
2709                                printer.pretty_named_argument_prefix("align"),
2710                                printer
2711                                    .numeric_literal_style()
2712                                    .apply(mem_layout.align.to_string())
2713                                    .into(),
2714                            ]),
2715                        ],
2716                        ")",
2717                    ),
2718                ]),
2719                qptr::shapes::GlobalVarShape::TypedInterface(ty) => {
2720                    printer.pretty_type_ascription_suffix(ty)
2721                }
2722            },
2723            TypeKind::SpvInst { spv_inst, type_and_const_inputs }
2724                if spv_inst.opcode == wk.OpTypePointer =>
2725            {
2726                match type_and_const_inputs[..] {
2727                    [TypeOrConst::Type(ty)] => printer.pretty_type_ascription_suffix(ty),
2728                    _ => unreachable!(),
2729                }
2730            }
2731            _ => pretty::Fragment::new([
2732                ": ".into(),
2733                printer.error_style().apply("pointee_type_of").into(),
2734                "(".into(),
2735                type_of_ptr_to.print(printer),
2736                ")".into(),
2737            ]),
2738        };
2739        let addr_space_suffix = match addr_space {
2740            AddrSpace::Handles => pretty::Fragment::default(),
2741            AddrSpace::SpvStorageClass(_) => {
2742                pretty::Fragment::new([" in ".into(), addr_space.print(printer)])
2743            }
2744        };
2745        let header = pretty::Fragment::new([addr_space_suffix, type_ascription_suffix]);
2746
2747        let maybe_rhs = match def {
2748            DeclDef::Imported(import) => Some(import.print(printer)),
2749            DeclDef::Present(GlobalVarDefBody { initializer }) => {
2750                // FIXME(eddyb) `global_varX in AS: T = Y` feels a bit wonky for
2751                // the initializer, but it's cleaner than obvious alternatives.
2752                initializer.map(|initializer| initializer.print(printer))
2753            }
2754        };
2755        let body = maybe_rhs.map(|rhs| pretty::Fragment::new(["= ".into(), rhs]));
2756
2757        let def_without_name = pretty::Fragment::new([header, pretty::join_space("", body)]);
2758
2759        AttrsAndDef { attrs: attrs.print(printer), def_without_name }
2760    }
2761}
2762
2763impl Print for AddrSpace {
2764    type Output = pretty::Fragment;
2765    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2766        match *self {
2767            AddrSpace::Handles => printer.declarative_keyword_style().apply("handles").into(),
2768            AddrSpace::SpvStorageClass(sc) => {
2769                let wk = &spv::spec::Spec::get().well_known;
2770                printer.pretty_spv_imm(wk.StorageClass, sc)
2771            }
2772        }
2773    }
2774}
2775
2776impl Print for FuncDecl {
2777    type Output = AttrsAndDef;
2778    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
2779        let Self { attrs, ret_type, params, def } = self;
2780
2781        let sig = pretty::Fragment::new([
2782            pretty::join_comma_sep(
2783                "(",
2784                params.iter().enumerate().map(|(i, param)| {
2785                    let param_name = match def {
2786                        DeclDef::Imported(_) => "_".into(),
2787                        DeclDef::Present(def) => Value::ControlRegionInput {
2788                            region: def.body,
2789                            input_idx: i.try_into().unwrap(),
2790                        }
2791                        .print_as_def(printer),
2792                    };
2793                    param.print(printer).insert_name_before_def(param_name)
2794                }),
2795                ")",
2796            ),
2797            " -> ".into(),
2798            ret_type.print(printer),
2799        ]);
2800
2801        let def_without_name = match def {
2802            DeclDef::Imported(import) => {
2803                pretty::Fragment::new([sig, " = ".into(), import.print(printer)])
2804            }
2805
2806            // FIXME(eddyb) this can probably go into `impl Print for FuncDefBody`.
2807            DeclDef::Present(def) => pretty::Fragment::new([
2808                sig,
2809                " {".into(),
2810                pretty::Node::IndentedBlock(match &def.unstructured_cfg {
2811                    None => vec![def.at_body().print(printer)],
2812                    Some(cfg) => cfg
2813                        .rev_post_order(def)
2814                        .map(|region| {
2815                            let label = Use::ControlRegionLabel(region);
2816                            let label_header = if printer.use_styles.contains_key(&label) {
2817                                let inputs = &def.at(region).def().inputs;
2818                                let label_inputs = if !inputs.is_empty() {
2819                                    pretty::join_comma_sep(
2820                                        "(",
2821                                        inputs.iter().enumerate().map(|(input_idx, input)| {
2822                                            input.print(printer).insert_name_before_def(
2823                                                Value::ControlRegionInput {
2824                                                    region,
2825                                                    input_idx: input_idx.try_into().unwrap(),
2826                                                }
2827                                                .print_as_def(printer),
2828                                            )
2829                                        }),
2830                                        ")",
2831                                    )
2832                                } else {
2833                                    pretty::Fragment::default()
2834                                };
2835
2836                                // FIXME(eddyb) `:` as used here for C-like "label syntax"
2837                                // interferes (in theory) with `e: T` "type ascription syntax".
2838                                pretty::Fragment::new([
2839                                    pretty::Node::ForceLineSeparation.into(),
2840                                    label.print_as_def(printer),
2841                                    label_inputs,
2842                                    ":".into(),
2843                                    pretty::Node::ForceLineSeparation.into(),
2844                                ])
2845                            } else {
2846                                pretty::Fragment::default()
2847                            };
2848
2849                            pretty::Fragment::new([
2850                                label_header,
2851                                pretty::Node::IndentedBlock(vec![def.at(region).print(printer)])
2852                                    .into(),
2853                                cfg.control_inst_on_exit_from[region].print(printer),
2854                            ])
2855                        })
2856                        .intersperse({
2857                            // Separate (top-level) control nodes with empty lines.
2858                            // FIXME(eddyb) have an explicit `pretty::Node`
2859                            // for "vertical gap" instead.
2860                            "\n\n".into()
2861                        })
2862                        .collect(),
2863                })
2864                .into(),
2865                "}".into(),
2866            ]),
2867        };
2868
2869        AttrsAndDef { attrs: attrs.print(printer), def_without_name }
2870    }
2871}
2872
2873impl Print for FuncParam {
2874    type Output = AttrsAndDef;
2875    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
2876        let Self { attrs, ty } = *self;
2877
2878        AttrsAndDef {
2879            attrs: attrs.print(printer),
2880            def_without_name: printer.pretty_type_ascription_suffix(ty),
2881        }
2882    }
2883}
2884
2885impl Print for FuncAt<'_, ControlRegion> {
2886    type Output = pretty::Fragment;
2887    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2888        let ControlRegionDef { inputs: _, children, outputs } = self.def();
2889
2890        // NOTE(eddyb) `inputs` are always printed by the parent.
2891
2892        let outputs_footer = if !outputs.is_empty() {
2893            let mut outputs = outputs.iter().map(|v| v.print(printer));
2894            let outputs = if outputs.len() == 1 {
2895                outputs.next().unwrap()
2896            } else {
2897                pretty::join_comma_sep("(", outputs, ")")
2898            };
2899            pretty::Fragment::new([pretty::Node::ForceLineSeparation.into(), outputs])
2900        } else {
2901            pretty::Fragment::default()
2902        };
2903
2904        pretty::Fragment::new([
2905            Use::AlignmentAnchorForControlRegion(self.position).print_as_def(printer),
2906            self.at(*children).into_iter().print(printer),
2907            outputs_footer,
2908        ])
2909    }
2910}
2911
2912impl Print for FuncAt<'_, EntityListIter<ControlNode>> {
2913    type Output = pretty::Fragment;
2914    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2915        pretty::Fragment::new(
2916            self.map(|func_at_control_node| func_at_control_node.print(printer))
2917                .intersperse(pretty::Node::ForceLineSeparation.into()),
2918        )
2919    }
2920}
2921
2922impl Print for FuncAt<'_, ControlNode> {
2923    type Output = pretty::Fragment;
2924    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
2925        let control_node = self.position;
2926        let ControlNodeDef { kind, outputs } = self.def();
2927
2928        let outputs_header = if !outputs.is_empty() {
2929            let mut outputs = outputs.iter().enumerate().map(|(output_idx, output)| {
2930                output.print(printer).insert_name_before_def(
2931                    Value::ControlNodeOutput {
2932                        control_node,
2933                        output_idx: output_idx.try_into().unwrap(),
2934                    }
2935                    .print_as_def(printer),
2936                )
2937            });
2938            let outputs_lhs = if outputs.len() == 1 {
2939                outputs.next().unwrap()
2940            } else {
2941                pretty::join_comma_sep("(", outputs, ")")
2942            };
2943            pretty::Fragment::new([outputs_lhs, " = ".into()])
2944        } else {
2945            pretty::Fragment::default()
2946        };
2947
2948        // FIXME(eddyb) using `declarative_keyword_style` seems more
2949        // appropriate here, but it's harder to spot at a glance.
2950        let kw_style = printer.imperative_keyword_style();
2951        let kw = |kw| kw_style.apply(kw).into();
2952        let control_node_body = match kind {
2953            ControlNodeKind::Block { insts } => {
2954                assert!(outputs.is_empty());
2955
2956                pretty::Fragment::new(
2957                    self.at(*insts)
2958                        .into_iter()
2959                        .map(|func_at_inst| func_at_inst.print(printer))
2960                        .flat_map(|entry| [pretty::Node::ForceLineSeparation.into(), entry]),
2961                )
2962            }
2963            ControlNodeKind::Select { kind, scrutinee, cases } => kind
2964                .print_with_scrutinee_and_cases(
2965                    printer,
2966                    kw_style,
2967                    *scrutinee,
2968                    cases.iter().map(|&case| self.at(case).print(printer)),
2969                ),
2970            ControlNodeKind::Loop { initial_inputs, body, repeat_condition } => {
2971                assert!(outputs.is_empty());
2972
2973                let inputs = &self.at(*body).def().inputs;
2974                assert_eq!(initial_inputs.len(), inputs.len());
2975
2976                // FIXME(eddyb) this avoids customizing how `body` is printed,
2977                // by adding a `-> ...` suffix to it instead, e.g. this `body`:
2978                // ```
2979                // v3 = ...
2980                // v4 = ...
2981                // (v3, v4)
2982                // ```
2983                // may be printed like this, as part of a loop:
2984                // ```
2985                // loop(v1 <- 0, v2 <- false) {
2986                //   v3 = ...
2987                //   v4 = ...
2988                //   (v3, v4) -> (v1, v2)
2989                // }
2990                // ```
2991                // In the above example, `v1` and `v2` are the `inputs` of the
2992                // `body`, which start at `0`/`false`, and are replaced with
2993                // `v3`/`v4` after each iteration.
2994                let (inputs_header, body_suffix) = if !inputs.is_empty() {
2995                    let input_decls_and_uses =
2996                        inputs.iter().enumerate().map(|(input_idx, input)| {
2997                            (input, Value::ControlRegionInput {
2998                                region: *body,
2999                                input_idx: input_idx.try_into().unwrap(),
3000                            })
3001                        });
3002                    (
3003                        pretty::join_comma_sep(
3004                            "(",
3005                            input_decls_and_uses.clone().zip(initial_inputs).map(
3006                                |((input_decl, input_use), initial)| {
3007                                    pretty::Fragment::new([
3008                                        input_decl.print(printer).insert_name_before_def(
3009                                            input_use.print_as_def(printer),
3010                                        ),
3011                                        " <- ".into(),
3012                                        initial.print(printer),
3013                                    ])
3014                                },
3015                            ),
3016                            ")",
3017                        ),
3018                        pretty::Fragment::new([" -> ".into(), {
3019                            let mut input_dests =
3020                                input_decls_and_uses.map(|(_, input_use)| input_use.print(printer));
3021                            if input_dests.len() == 1 {
3022                                input_dests.next().unwrap()
3023                            } else {
3024                                pretty::join_comma_sep("(", input_dests, ")")
3025                            }
3026                        }]),
3027                    )
3028                } else {
3029                    (pretty::Fragment::default(), pretty::Fragment::default())
3030                };
3031
3032                // FIXME(eddyb) this is a weird mishmash of Rust and C syntax.
3033                pretty::Fragment::new([
3034                    kw("loop"),
3035                    inputs_header,
3036                    " {".into(),
3037                    pretty::Node::IndentedBlock(vec![pretty::Fragment::new([
3038                        self.at(*body).print(printer),
3039                        body_suffix,
3040                    ])])
3041                    .into(),
3042                    "} ".into(),
3043                    kw("while"),
3044                    " ".into(),
3045                    repeat_condition.print(printer),
3046                ])
3047            }
3048            ControlNodeKind::ExitInvocation {
3049                kind: cfg::ExitInvocationKind::SpvInst(spv::Inst { opcode, imms }),
3050                inputs,
3051            } => printer.pretty_spv_inst(
3052                kw_style,
3053                *opcode,
3054                imms,
3055                inputs.iter().map(|v| v.print(printer)),
3056            ),
3057        };
3058        pretty::Fragment::new([
3059            Use::AlignmentAnchorForControlNode(self.position).print_as_def(printer),
3060            outputs_header,
3061            control_node_body,
3062        ])
3063    }
3064}
3065
3066impl Print for ControlRegionInputDecl {
3067    type Output = AttrsAndDef;
3068    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
3069        let Self { attrs, ty } = *self;
3070
3071        AttrsAndDef {
3072            attrs: attrs.print(printer),
3073            def_without_name: printer.pretty_type_ascription_suffix(ty),
3074        }
3075    }
3076}
3077
3078impl Print for ControlNodeOutputDecl {
3079    type Output = AttrsAndDef;
3080    fn print(&self, printer: &Printer<'_>) -> AttrsAndDef {
3081        let Self { attrs, ty } = *self;
3082
3083        AttrsAndDef {
3084            attrs: attrs.print(printer),
3085            def_without_name: printer.pretty_type_ascription_suffix(ty),
3086        }
3087    }
3088}
3089
3090impl Print for FuncAt<'_, DataInst> {
3091    type Output = pretty::Fragment;
3092    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
3093        let DataInstDef { attrs, form, inputs } = self.def();
3094
3095        let attrs = attrs.print(printer);
3096
3097        let DataInstFormDef { kind, output_type } = &printer.cx[*form];
3098
3099        let mut output_use_to_print_as_lhs =
3100            output_type.map(|_| Use::DataInstOutput(self.position));
3101
3102        let mut output_type_to_print = *output_type;
3103
3104        let def_without_type = match kind {
3105            &DataInstKind::FuncCall(func) => pretty::Fragment::new([
3106                printer.declarative_keyword_style().apply("call").into(),
3107                " ".into(),
3108                func.print(printer),
3109                pretty::join_comma_sep("(", inputs.iter().map(|v| v.print(printer)), ")"),
3110            ]),
3111
3112            DataInstKind::QPtr(op) => {
3113                let (qptr_input, extra_inputs) = match op {
3114                    // HACK(eddyb) `FuncLocalVar` should probably not even be in `QPtrOp`.
3115                    QPtrOp::FuncLocalVar(_) => (None, &inputs[..]),
3116                    _ => (Some(inputs[0]), &inputs[1..]),
3117                };
3118                let (name, extra_inputs): (_, SmallVec<[_; 1]>) = match op {
3119                    QPtrOp::FuncLocalVar(mem_layout) => {
3120                        assert!(extra_inputs.len() <= 1);
3121                        (
3122                            "func_local_var",
3123                            [
3124                                pretty::Fragment::new([
3125                                    printer.pretty_named_argument_prefix("size"),
3126                                    printer
3127                                        .numeric_literal_style()
3128                                        .apply(mem_layout.size.to_string())
3129                                        .into(),
3130                                ]),
3131                                pretty::Fragment::new([
3132                                    printer.pretty_named_argument_prefix("align"),
3133                                    printer
3134                                        .numeric_literal_style()
3135                                        .apply(mem_layout.align.to_string())
3136                                        .into(),
3137                                ]),
3138                            ]
3139                            .into_iter()
3140                            .chain(extra_inputs.first().map(|&init| {
3141                                pretty::Fragment::new([
3142                                    printer.pretty_named_argument_prefix("initializer"),
3143                                    init.print(printer),
3144                                ])
3145                            }))
3146                            .collect(),
3147                        )
3148                    }
3149
3150                    QPtrOp::HandleArrayIndex => {
3151                        assert_eq!(extra_inputs.len(), 1);
3152                        (
3153                            "handle_array_index",
3154                            [extra_inputs[0].print(printer)].into_iter().collect(),
3155                        )
3156                    }
3157                    QPtrOp::BufferData => {
3158                        assert_eq!(extra_inputs.len(), 0);
3159                        ("buffer_data", [].into_iter().collect())
3160                    }
3161                    &QPtrOp::BufferDynLen { fixed_base_size, dyn_unit_stride } => {
3162                        assert_eq!(extra_inputs.len(), 0);
3163
3164                        // FIXME(eddyb) this isn't very nice, but without mapping
3165                        // to actual integer ops, there's not a lot of options.
3166                        (
3167                            "buffer_dyn_len",
3168                            [
3169                                pretty::Fragment::new([
3170                                    printer.pretty_named_argument_prefix("fixed_base_size"),
3171                                    printer
3172                                        .numeric_literal_style()
3173                                        .apply(fixed_base_size.to_string())
3174                                        .into(),
3175                                ]),
3176                                pretty::Fragment::new([
3177                                    printer.pretty_named_argument_prefix("dyn_unit_stride"),
3178                                    printer
3179                                        .numeric_literal_style()
3180                                        .apply(dyn_unit_stride.to_string())
3181                                        .into(),
3182                                ]),
3183                            ]
3184                            .into_iter()
3185                            .collect(),
3186                        )
3187                    }
3188
3189                    QPtrOp::Offset(offset) => {
3190                        assert_eq!(extra_inputs.len(), 0);
3191                        (
3192                            "offset",
3193                            [printer.numeric_literal_style().apply(format!("{offset}")).into()]
3194                                .into_iter()
3195                                .collect(),
3196                        )
3197                    }
3198                    &QPtrOp::DynOffset { stride, index_bounds: _ } => {
3199                        assert_eq!(extra_inputs.len(), 1);
3200                        (
3201                            "dyn_offset",
3202                            [pretty::Fragment::new([
3203                                extra_inputs[0].print(printer),
3204                                " × ".into(),
3205                                printer.numeric_literal_style().apply(format!("{stride}")).into(),
3206                            ])]
3207                            .into_iter()
3208                            .collect(),
3209                        )
3210                    }
3211
3212                    QPtrOp::Load => {
3213                        assert_eq!(extra_inputs.len(), 0);
3214                        ("load", [].into_iter().collect())
3215                    }
3216                    QPtrOp::Store => {
3217                        assert_eq!(extra_inputs.len(), 1);
3218                        ("store", [extra_inputs[0].print(printer)].into_iter().collect())
3219                    }
3220                };
3221
3222                pretty::Fragment::new([
3223                    printer
3224                        .demote_style_for_namespace_prefix(printer.declarative_keyword_style())
3225                        .apply("qptr.")
3226                        .into(),
3227                    printer.declarative_keyword_style().apply(name).into(),
3228                    pretty::join_comma_sep(
3229                        "(",
3230                        qptr_input.map(|v| v.print(printer)).into_iter().chain(extra_inputs),
3231                        ")",
3232                    ),
3233                ])
3234            }
3235
3236            DataInstKind::SpvInst(inst) => printer.pretty_spv_inst(
3237                printer.spv_op_style(),
3238                inst.opcode,
3239                &inst.imms,
3240                inputs.iter().map(|v| v.print(printer)),
3241            ),
3242            &DataInstKind::SpvExtInst { ext_set, inst } => {
3243                let spv_spec = spv::spec::Spec::get();
3244                let wk = &spv_spec.well_known;
3245
3246                // HACK(eddyb) hide `OpTypeVoid` types, as they're effectively
3247                // the default, and not meaningful *even if* the resulting
3248                // value is "used" in a kind of "untyped token" way.
3249                output_type_to_print = output_type_to_print.filter(|&ty| {
3250                    let is_void = match &printer.cx[ty].kind {
3251                        TypeKind::SpvInst { spv_inst, .. } => spv_inst.opcode == wk.OpTypeVoid,
3252                        _ => false,
3253                    };
3254                    !is_void
3255                });
3256                // HACK(eddyb) only keep around untyped outputs if they're used.
3257                if output_type_to_print.is_none() {
3258                    output_use_to_print_as_lhs = output_use_to_print_as_lhs.filter(|output_use| {
3259                        printer
3260                            .use_styles
3261                            .get(output_use)
3262                            .is_some_and(|style| !matches!(style, UseStyle::Inline))
3263                    });
3264                }
3265
3266                // FIXME(eddyb) this may get expensive, cache it?
3267                let ext_set_name = &printer.cx[ext_set];
3268                let lowercase_ext_set_name = ext_set_name.to_ascii_lowercase();
3269                let (ext_set_alias, known_inst_desc) = (spv_spec
3270                    .get_ext_inst_set_by_lowercase_name(&lowercase_ext_set_name))
3271                .or_else(|| {
3272                    printer.cx.get_custom_ext_inst_set_by_lowercase_name(&lowercase_ext_set_name)
3273                })
3274                .map_or((&None, None), |ext_inst_set| {
3275                    // FIXME(eddyb) check that these aliases are unique
3276                    // across the entire output before using them!
3277                    (&ext_inst_set.short_alias, ext_inst_set.instructions.get(&inst))
3278                });
3279
3280                // FIXME(eddyb) extract and separate out the version?
3281                let ext_set_name = ext_set_alias.as_deref().unwrap_or(ext_set_name);
3282
3283                // HACK(eddyb) infinite iterator, only to be used with `zip`.
3284                let operand_names = known_inst_desc
3285                    .into_iter()
3286                    .flat_map(|inst_desc| inst_desc.operand_names.iter().map(|name| &name[..]))
3287                    .chain(std::iter::repeat(""));
3288
3289                // HACK(eddyb) we only support two kinds of "pseudo-immediates"
3290                // (i.e. `Const`s used as immediates by extended instruction sets).
3291                enum PseudoImm<'a> {
3292                    Str(&'a str),
3293                    U32(u32),
3294                }
3295                let pseudo_imm_from_value = |v: Value| {
3296                    if let Value::Const(ct) = v {
3297                        match &printer.cx[ct].kind {
3298                            &ConstKind::SpvStringLiteralForExtInst(s) => {
3299                                return Some(PseudoImm::Str(&printer.cx[s]));
3300                            }
3301                            ConstKind::SpvInst { spv_inst_and_const_inputs } => {
3302                                let (spv_inst, _const_inputs) = &**spv_inst_and_const_inputs;
3303                                if spv_inst.opcode == wk.OpConstant {
3304                                    if let [spv::Imm::Short(_, x)] = spv_inst.imms[..] {
3305                                        // HACK(eddyb) only allow unambiguously positive values.
3306                                        if i32::try_from(x).and_then(u32::try_from) == Ok(x) {
3307                                            return Some(PseudoImm::U32(x));
3308                                        }
3309                                    }
3310                                }
3311                            }
3312                            ConstKind::PtrToGlobalVar(_) => {}
3313                        }
3314                    }
3315                    None
3316                };
3317
3318                let debuginfo_with_pseudo_imm_inputs: Option<SmallVec<[_; 8]>> = known_inst_desc
3319                    .filter(|inst_desc| {
3320                        inst_desc.is_debuginfo && output_use_to_print_as_lhs.is_none()
3321                    })
3322                    .and_then(|_| inputs.iter().copied().map(pseudo_imm_from_value).collect());
3323                let printing_debuginfo_as_comment = debuginfo_with_pseudo_imm_inputs.is_some();
3324
3325                let [spv_base_style, string_literal_style, numeric_literal_style] =
3326                    if printing_debuginfo_as_comment {
3327                        [printer.comment_style(); 3]
3328                    } else {
3329                        [
3330                            printer.spv_base_style(),
3331                            printer.string_literal_style(),
3332                            printer.numeric_literal_style(),
3333                        ]
3334                    };
3335
3336                let inst_name_or_num = {
3337                    let (style, s) = match known_inst_desc {
3338                        Some(inst_desc) => (spv_base_style, inst_desc.name.clone()),
3339                        None => (numeric_literal_style, format!("{inst}").into()),
3340                    };
3341                    // HACK(eddyb) this overlaps a bit with `Printer::spv_op_style`.
3342                    pretty::Styles { thickness: Some(3), ..style }.apply(s)
3343                };
3344
3345                pretty::Fragment::new([
3346                    if printing_debuginfo_as_comment {
3347                        printer.comment_style().apply("// ").into()
3348                    } else {
3349                        pretty::Fragment::default()
3350                    },
3351                    // HACK(eddyb) double/triple-demote to end up with `spv.extinst.A.B`,
3352                    // with increasing size from `spv.` to `extinst.` to `A.` to `B`.
3353                    printer
3354                        .demote_style_for_namespace_prefix(
3355                            printer.demote_style_for_namespace_prefix(
3356                                printer.demote_style_for_namespace_prefix(spv_base_style),
3357                            ),
3358                        )
3359                        .apply("spv.")
3360                        .into(),
3361                    printer
3362                        .demote_style_for_namespace_prefix(
3363                            printer.demote_style_for_namespace_prefix(spv_base_style),
3364                        )
3365                        .apply("extinst.")
3366                        .into(),
3367                    // HACK(eddyb) print it as a string still, since we don't sanitize it.
3368                    printer
3369                        .demote_style_for_namespace_prefix(string_literal_style)
3370                        .apply(format!("{ext_set_name:?}"))
3371                        .into(),
3372                    printer.demote_style_for_namespace_prefix(spv_base_style).apply(".").into(),
3373                    inst_name_or_num.into(),
3374                    if let Some(inputs) = debuginfo_with_pseudo_imm_inputs {
3375                        let style = printer.comment_style();
3376                        let inputs = inputs.into_iter().zip(operand_names).map(|(input, name)| {
3377                            pretty::Fragment::new([
3378                                Some(name)
3379                                    .filter(|name| !name.is_empty())
3380                                    .and_then(|name| {
3381                                        Some(printer.pretty_named_argument_prefix(
3382                                            printer.sanitize_spv_operand_name(name)?,
3383                                        ))
3384                                    })
3385                                    .unwrap_or_default(),
3386                                style
3387                                    .apply(match input {
3388                                        PseudoImm::Str(s) => format!("{s:?}"),
3389                                        PseudoImm::U32(x) => format!("{x}"),
3390                                    })
3391                                    .into(),
3392                            ])
3393                        });
3394                        pretty::Fragment::new(
3395                            ([style.apply("(").into()].into_iter())
3396                                .chain(inputs.intersperse(style.apply(", ").into()))
3397                                .chain([style.apply(")").into()]),
3398                        )
3399                    } else {
3400                        pretty::join_comma_sep(
3401                            "(",
3402                            inputs.iter().zip(operand_names).map(|(&input, name)| {
3403                                // HACK(eddyb) no need to wrap strings in `OpString(...)`.
3404                                let printed_input = match pseudo_imm_from_value(input) {
3405                                    Some(PseudoImm::Str(s)) => printer.pretty_string_literal(s),
3406                                    _ => input.print(printer),
3407                                };
3408                                let name = Some(name)
3409                                    .filter(|name| !name.is_empty())
3410                                    .and_then(|name| printer.sanitize_spv_operand_name(name));
3411                                if let Some(name) = name {
3412                                    pretty::Fragment::new([
3413                                        // HACK(eddyb) this duplicates part of
3414                                        // `Printer::pretty_named_argument_prefix`,
3415                                        // but the `pretty::join_space` is important.
3416                                        printer
3417                                            .named_argument_label_style()
3418                                            .apply(format!("{name}:"))
3419                                            .into(),
3420                                        pretty::join_space("", [printed_input]),
3421                                    ])
3422                                } else {
3423                                    printed_input
3424                                }
3425                            }),
3426                            ")",
3427                        )
3428                    },
3429                ])
3430            }
3431        };
3432
3433        let def_without_name = pretty::Fragment::new([
3434            def_without_type,
3435            output_type_to_print
3436                .map(|ty| printer.pretty_type_ascription_suffix(ty))
3437                .unwrap_or_default(),
3438        ]);
3439
3440        // FIXME(eddyb) this is quite verbose for prepending.
3441        let def_without_name = pretty::Fragment::new([
3442            Use::AlignmentAnchorForDataInst(self.position).print_as_def(printer),
3443            def_without_name,
3444        ]);
3445
3446        AttrsAndDef { attrs, def_without_name }.insert_name_before_def(
3447            output_use_to_print_as_lhs
3448                .map(|output_use| {
3449                    pretty::Fragment::new([output_use.print_as_def(printer), " = ".into()])
3450                })
3451                .unwrap_or_default(),
3452        )
3453    }
3454}
3455
3456impl Print for cfg::ControlInst {
3457    type Output = pretty::Fragment;
3458    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
3459        let Self { attrs, kind, inputs, targets, target_inputs } = self;
3460
3461        let attrs = attrs.print(printer);
3462
3463        let kw_style = printer.imperative_keyword_style();
3464        let kw = |kw| kw_style.apply(kw).into();
3465
3466        let mut targets = targets.iter().map(|&target_region| {
3467            let mut target = pretty::Fragment::new([
3468                kw("branch"),
3469                " ".into(),
3470                Use::ControlRegionLabel(target_region).print(printer),
3471            ]);
3472            if let Some(inputs) = target_inputs.get(&target_region) {
3473                target = pretty::Fragment::new([
3474                    target,
3475                    pretty::join_comma_sep("(", inputs.iter().map(|v| v.print(printer)), ")"),
3476                ]);
3477            }
3478            target
3479        });
3480
3481        let def = match kind {
3482            cfg::ControlInstKind::Unreachable => {
3483                // FIXME(eddyb) use `targets.is_empty()` when that is stabilized.
3484                assert!(targets.len() == 0 && inputs.is_empty());
3485                kw("unreachable")
3486            }
3487            cfg::ControlInstKind::Return => {
3488                // FIXME(eddyb) use `targets.is_empty()` when that is stabilized.
3489                assert!(targets.len() == 0);
3490                match inputs[..] {
3491                    [] => kw("return"),
3492                    [v] => pretty::Fragment::new([kw("return"), " ".into(), v.print(printer)]),
3493                    _ => unreachable!(),
3494                }
3495            }
3496            cfg::ControlInstKind::ExitInvocation(cfg::ExitInvocationKind::SpvInst(spv::Inst {
3497                opcode,
3498                imms,
3499            })) => {
3500                // FIXME(eddyb) use `targets.is_empty()` when that is stabilized.
3501                assert!(targets.len() == 0);
3502                printer.pretty_spv_inst(
3503                    kw_style,
3504                    *opcode,
3505                    imms,
3506                    inputs.iter().map(|v| v.print(printer)),
3507                )
3508            }
3509
3510            cfg::ControlInstKind::Branch => {
3511                assert_eq!((targets.len(), inputs.len()), (1, 0));
3512                targets.next().unwrap()
3513            }
3514
3515            cfg::ControlInstKind::SelectBranch(kind) => {
3516                assert_eq!(inputs.len(), 1);
3517                kind.print_with_scrutinee_and_cases(printer, kw_style, inputs[0], targets)
3518            }
3519        };
3520
3521        pretty::Fragment::new([attrs, def])
3522    }
3523}
3524
3525impl SelectionKind {
3526    fn print_with_scrutinee_and_cases(
3527        &self,
3528        printer: &Printer<'_>,
3529        kw_style: pretty::Styles,
3530        scrutinee: Value,
3531        mut cases: impl ExactSizeIterator<Item = pretty::Fragment>,
3532    ) -> pretty::Fragment {
3533        let kw = |kw| kw_style.apply(kw).into();
3534        match *self {
3535            SelectionKind::BoolCond => {
3536                assert_eq!(cases.len(), 2);
3537                let [then_case, else_case] = [cases.next().unwrap(), cases.next().unwrap()];
3538                pretty::Fragment::new([
3539                    kw("if"),
3540                    " ".into(),
3541                    scrutinee.print(printer),
3542                    " {".into(),
3543                    pretty::Node::IndentedBlock(vec![then_case]).into(),
3544                    "} ".into(),
3545                    kw("else"),
3546                    " {".into(),
3547                    pretty::Node::IndentedBlock(vec![else_case]).into(),
3548                    "}".into(),
3549                ])
3550            }
3551            SelectionKind::SpvInst(spv::Inst { opcode, ref imms }) => {
3552                let header = printer.pretty_spv_inst(
3553                    kw_style,
3554                    opcode,
3555                    imms,
3556                    [Some(scrutinee.print(printer))]
3557                        .into_iter()
3558                        .chain((0..cases.len()).map(|_| None)),
3559                );
3560
3561                pretty::Fragment::new([
3562                    header,
3563                    " {".into(),
3564                    pretty::Node::IndentedBlock(
3565                        cases
3566                            .map(|case| {
3567                                pretty::Fragment::new([
3568                                    pretty::Node::ForceLineSeparation.into(),
3569                                    // FIXME(eddyb) this should pull information out
3570                                    // of the instruction to be more precise.
3571                                    kw("case"),
3572                                    " => {".into(),
3573                                    pretty::Node::IndentedBlock(vec![case]).into(),
3574                                    "}".into(),
3575                                    pretty::Node::ForceLineSeparation.into(),
3576                                ])
3577                            })
3578                            .collect(),
3579                    )
3580                    .into(),
3581                    "}".into(),
3582                ])
3583            }
3584        }
3585    }
3586}
3587
3588impl Value {
3589    fn print_as_def(&self, printer: &Printer<'_>) -> pretty::Fragment {
3590        Use::from(*self).print_as_def(printer)
3591    }
3592}
3593
3594impl Print for Value {
3595    type Output = pretty::Fragment;
3596    fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
3597        Use::from(*self).print(printer)
3598    }
3599}