naga/back/dot/
mod.rs

1/*!
2Backend for [DOT][dot] (Graphviz).
3
4This backend writes a graph in the DOT language, for the ease
5of IR inspection and debugging.
6
7[dot]: https://graphviz.org/doc/info/lang.html
8*/
9
10use alloc::{
11    borrow::Cow,
12    format,
13    string::{String, ToString},
14    vec::Vec,
15};
16use core::fmt::{Error as FmtError, Write as _};
17
18use crate::{
19    arena::Handle,
20    valid::{FunctionInfo, ModuleInfo},
21};
22
23/// Configuration options for the dot backend
24#[derive(Clone, Default)]
25pub struct Options {
26    /// Only emit function bodies
27    pub cfg_only: bool,
28}
29
30/// Identifier used to address a graph node
31type NodeId = usize;
32
33/// Stores the target nodes for control flow statements
34#[derive(Default, Clone, Copy)]
35struct Targets {
36    /// The node, if some, where continue operations will land
37    continue_target: Option<usize>,
38    /// The node, if some, where break operations will land
39    break_target: Option<usize>,
40}
41
42/// Stores information about the graph of statements
43#[derive(Default)]
44struct StatementGraph {
45    /// List of node names
46    nodes: Vec<&'static str>,
47    /// List of edges of the control flow, the items are defined as
48    /// (from, to, label)
49    flow: Vec<(NodeId, NodeId, &'static str)>,
50    /// List of implicit edges of the control flow, used for jump
51    /// operations such as continue or break, the items are defined as
52    /// (from, to, label, color_id)
53    jumps: Vec<(NodeId, NodeId, &'static str, usize)>,
54    /// List of dependency relationships between a statement node and
55    /// expressions
56    dependencies: Vec<(NodeId, Handle<crate::Expression>, &'static str)>,
57    /// List of expression emitted by statement node
58    emits: Vec<(NodeId, Handle<crate::Expression>)>,
59    /// List of function call by statement node
60    calls: Vec<(NodeId, Handle<crate::Function>)>,
61}
62
63impl StatementGraph {
64    /// Adds a new block to the statement graph, returning the first and last node, respectively
65    fn add(&mut self, block: &[crate::Statement], targets: Targets) -> (NodeId, NodeId) {
66        use crate::Statement as S;
67
68        // The first node of the block isn't a statement but a virtual node
69        let root = self.nodes.len();
70        self.nodes.push(if root == 0 { "Root" } else { "Node" });
71        // Track the last placed node, this will be returned to the caller and
72        // will also be used to generate the control flow edges
73        let mut last_node = root;
74        for statement in block {
75            // Reserve a new node for the current statement and link it to the
76            // node of the previous statement
77            let id = self.nodes.len();
78            self.flow.push((last_node, id, ""));
79            self.nodes.push(""); // reserve space
80
81            // Track the node identifier for the merge node, the merge node is
82            // the last node of a statement, normally this is the node itself,
83            // but for control flow statements such as `if`s and `switch`s this
84            // is a virtual node where all branches merge back.
85            let mut merge_id = id;
86
87            self.nodes[id] = match *statement {
88                S::Emit(ref range) => {
89                    for handle in range.clone() {
90                        self.emits.push((id, handle));
91                    }
92                    "Emit"
93                }
94                S::Kill => "Kill", //TODO: link to the beginning
95                S::Break => {
96                    // Try to link to the break target, otherwise produce
97                    // a broken connection
98                    if let Some(target) = targets.break_target {
99                        self.jumps.push((id, target, "Break", 5))
100                    } else {
101                        self.jumps.push((id, root, "Broken", 7))
102                    }
103                    "Break"
104                }
105                S::Continue => {
106                    // Try to link to the continue target, otherwise produce
107                    // a broken connection
108                    if let Some(target) = targets.continue_target {
109                        self.jumps.push((id, target, "Continue", 5))
110                    } else {
111                        self.jumps.push((id, root, "Broken", 7))
112                    }
113                    "Continue"
114                }
115                S::ControlBarrier(_flags) => "ControlBarrier",
116                S::MemoryBarrier(_flags) => "MemoryBarrier",
117                S::Block(ref b) => {
118                    let (other, last) = self.add(b, targets);
119                    self.flow.push((id, other, ""));
120                    // All following nodes should connect to the end of the block
121                    // statement so change the merge id to it.
122                    merge_id = last;
123                    "Block"
124                }
125                S::If {
126                    condition,
127                    ref accept,
128                    ref reject,
129                } => {
130                    self.dependencies.push((id, condition, "condition"));
131                    let (accept_id, accept_last) = self.add(accept, targets);
132                    self.flow.push((id, accept_id, "accept"));
133                    let (reject_id, reject_last) = self.add(reject, targets);
134                    self.flow.push((id, reject_id, "reject"));
135
136                    // Create a merge node, link the branches to it and set it
137                    // as the merge node to make the next statement node link to it
138                    merge_id = self.nodes.len();
139                    self.nodes.push("Merge");
140                    self.flow.push((accept_last, merge_id, ""));
141                    self.flow.push((reject_last, merge_id, ""));
142
143                    "If"
144                }
145                S::Switch {
146                    selector,
147                    ref cases,
148                } => {
149                    self.dependencies.push((id, selector, "selector"));
150
151                    // Create a merge node and set it as the merge node to make
152                    // the next statement node link to it
153                    merge_id = self.nodes.len();
154                    self.nodes.push("Merge");
155
156                    // Create a new targets structure and set the break target
157                    // to the merge node
158                    let mut targets = targets;
159                    targets.break_target = Some(merge_id);
160
161                    for case in cases {
162                        let (case_id, case_last) = self.add(&case.body, targets);
163                        let label = match case.value {
164                            crate::SwitchValue::Default => "default",
165                            _ => "case",
166                        };
167                        self.flow.push((id, case_id, label));
168                        // Link the last node of the branch to the merge node
169                        self.flow.push((case_last, merge_id, ""));
170                    }
171                    "Switch"
172                }
173                S::Loop {
174                    ref body,
175                    ref continuing,
176                    break_if,
177                } => {
178                    // Create a new targets structure and set the break target
179                    // to the merge node, this must happen before generating the
180                    // continuing block since it can break.
181                    let mut targets = targets;
182                    targets.break_target = Some(id);
183
184                    let (continuing_id, continuing_last) = self.add(continuing, targets);
185
186                    // Set the the continue target to the beginning
187                    // of the newly generated continuing block
188                    targets.continue_target = Some(continuing_id);
189
190                    let (body_id, body_last) = self.add(body, targets);
191
192                    self.flow.push((id, body_id, "body"));
193
194                    // Link the last node of the body to the continuing block
195                    self.flow.push((body_last, continuing_id, "continuing"));
196                    // Link the last node of the continuing block back to the
197                    // beginning of the loop body
198                    self.flow.push((continuing_last, body_id, "continuing"));
199
200                    if let Some(expr) = break_if {
201                        self.dependencies.push((continuing_id, expr, "break if"));
202                    }
203
204                    "Loop"
205                }
206                S::Return { value } => {
207                    if let Some(expr) = value {
208                        self.dependencies.push((id, expr, "value"));
209                    }
210                    "Return"
211                }
212                S::Store { pointer, value } => {
213                    self.dependencies.push((id, value, "value"));
214                    self.emits.push((id, pointer));
215                    "Store"
216                }
217                S::ImageStore {
218                    image,
219                    coordinate,
220                    array_index,
221                    value,
222                } => {
223                    self.dependencies.push((id, image, "image"));
224                    self.dependencies.push((id, coordinate, "coordinate"));
225                    if let Some(expr) = array_index {
226                        self.dependencies.push((id, expr, "array_index"));
227                    }
228                    self.dependencies.push((id, value, "value"));
229                    "ImageStore"
230                }
231                S::Call {
232                    function,
233                    ref arguments,
234                    result,
235                } => {
236                    for &arg in arguments {
237                        self.dependencies.push((id, arg, "arg"));
238                    }
239                    if let Some(expr) = result {
240                        self.emits.push((id, expr));
241                    }
242                    self.calls.push((id, function));
243                    "Call"
244                }
245                S::Atomic {
246                    pointer,
247                    ref fun,
248                    value,
249                    result,
250                } => {
251                    if let Some(result) = result {
252                        self.emits.push((id, result));
253                    }
254                    self.dependencies.push((id, pointer, "pointer"));
255                    self.dependencies.push((id, value, "value"));
256                    if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun {
257                        self.dependencies.push((id, cmp, "cmp"));
258                    }
259                    "Atomic"
260                }
261                S::ImageAtomic {
262                    image,
263                    coordinate,
264                    array_index,
265                    fun: _,
266                    value,
267                } => {
268                    self.dependencies.push((id, image, "image"));
269                    self.dependencies.push((id, coordinate, "coordinate"));
270                    if let Some(expr) = array_index {
271                        self.dependencies.push((id, expr, "array_index"));
272                    }
273                    self.dependencies.push((id, value, "value"));
274                    "ImageAtomic"
275                }
276                S::WorkGroupUniformLoad { pointer, result } => {
277                    self.emits.push((id, result));
278                    self.dependencies.push((id, pointer, "pointer"));
279                    "WorkGroupUniformLoad"
280                }
281                S::RayQuery { query, ref fun } => {
282                    self.dependencies.push((id, query, "query"));
283                    match *fun {
284                        crate::RayQueryFunction::Initialize {
285                            acceleration_structure,
286                            descriptor,
287                        } => {
288                            self.dependencies.push((
289                                id,
290                                acceleration_structure,
291                                "acceleration_structure",
292                            ));
293                            self.dependencies.push((id, descriptor, "descriptor"));
294                            "RayQueryInitialize"
295                        }
296                        crate::RayQueryFunction::Proceed { result } => {
297                            self.emits.push((id, result));
298                            "RayQueryProceed"
299                        }
300                        crate::RayQueryFunction::GenerateIntersection { hit_t } => {
301                            self.dependencies.push((id, hit_t, "hit_t"));
302                            "RayQueryGenerateIntersection"
303                        }
304                        crate::RayQueryFunction::ConfirmIntersection => {
305                            "RayQueryConfirmIntersection"
306                        }
307                        crate::RayQueryFunction::Terminate => "RayQueryTerminate",
308                    }
309                }
310                S::SubgroupBallot { result, predicate } => {
311                    if let Some(predicate) = predicate {
312                        self.dependencies.push((id, predicate, "predicate"));
313                    }
314                    self.emits.push((id, result));
315                    "SubgroupBallot"
316                }
317                S::SubgroupCollectiveOperation {
318                    op,
319                    collective_op,
320                    argument,
321                    result,
322                } => {
323                    self.dependencies.push((id, argument, "arg"));
324                    self.emits.push((id, result));
325                    match (collective_op, op) {
326                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => {
327                            "SubgroupAll"
328                        }
329                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => {
330                            "SubgroupAny"
331                        }
332                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => {
333                            "SubgroupAdd"
334                        }
335                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => {
336                            "SubgroupMul"
337                        }
338                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => {
339                            "SubgroupMax"
340                        }
341                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => {
342                            "SubgroupMin"
343                        }
344                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => {
345                            "SubgroupAnd"
346                        }
347                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => {
348                            "SubgroupOr"
349                        }
350                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => {
351                            "SubgroupXor"
352                        }
353                        (
354                            crate::CollectiveOperation::ExclusiveScan,
355                            crate::SubgroupOperation::Add,
356                        ) => "SubgroupExclusiveAdd",
357                        (
358                            crate::CollectiveOperation::ExclusiveScan,
359                            crate::SubgroupOperation::Mul,
360                        ) => "SubgroupExclusiveMul",
361                        (
362                            crate::CollectiveOperation::InclusiveScan,
363                            crate::SubgroupOperation::Add,
364                        ) => "SubgroupInclusiveAdd",
365                        (
366                            crate::CollectiveOperation::InclusiveScan,
367                            crate::SubgroupOperation::Mul,
368                        ) => "SubgroupInclusiveMul",
369                        _ => unimplemented!(),
370                    }
371                }
372                S::SubgroupGather {
373                    mode,
374                    argument,
375                    result,
376                } => {
377                    match mode {
378                        crate::GatherMode::BroadcastFirst => {}
379                        crate::GatherMode::Broadcast(index)
380                        | crate::GatherMode::Shuffle(index)
381                        | crate::GatherMode::ShuffleDown(index)
382                        | crate::GatherMode::ShuffleUp(index)
383                        | crate::GatherMode::ShuffleXor(index)
384                        | crate::GatherMode::QuadBroadcast(index) => {
385                            self.dependencies.push((id, index, "index"))
386                        }
387                        crate::GatherMode::QuadSwap(_) => {}
388                    }
389                    self.dependencies.push((id, argument, "arg"));
390                    self.emits.push((id, result));
391                    match mode {
392                        crate::GatherMode::BroadcastFirst => "SubgroupBroadcastFirst",
393                        crate::GatherMode::Broadcast(_) => "SubgroupBroadcast",
394                        crate::GatherMode::Shuffle(_) => "SubgroupShuffle",
395                        crate::GatherMode::ShuffleDown(_) => "SubgroupShuffleDown",
396                        crate::GatherMode::ShuffleUp(_) => "SubgroupShuffleUp",
397                        crate::GatherMode::ShuffleXor(_) => "SubgroupShuffleXor",
398                        crate::GatherMode::QuadBroadcast(_) => "SubgroupQuadBroadcast",
399                        crate::GatherMode::QuadSwap(direction) => match direction {
400                            crate::Direction::X => "SubgroupQuadSwapX",
401                            crate::Direction::Y => "SubgroupQuadSwapY",
402                            crate::Direction::Diagonal => "SubgroupQuadSwapDiagonal",
403                        },
404                    }
405                }
406                S::CooperativeStore { target, data } => {
407                    self.dependencies.push((id, target, "target"));
408                    self.dependencies.push((id, data.pointer, "pointer"));
409                    self.dependencies.push((id, data.stride, "stride"));
410                    if data.row_major {
411                        "CoopStoreT"
412                    } else {
413                        "CoopStore"
414                    }
415                }
416                S::RayPipelineFunction(func) => match func {
417                    crate::RayPipelineFunction::TraceRay {
418                        acceleration_structure,
419                        descriptor,
420                        payload,
421                    } => {
422                        self.dependencies.push((
423                            id,
424                            acceleration_structure,
425                            "acceleration_structure",
426                        ));
427                        self.dependencies.push((id, descriptor, "descriptor"));
428                        self.dependencies.push((id, payload, "payload"));
429                        "TraceRay"
430                    }
431                },
432            };
433            // Set the last node to the merge node
434            last_node = merge_id;
435        }
436        (root, last_node)
437    }
438}
439
440fn name(option: &Option<String>) -> &str {
441    option.as_deref().unwrap_or_default()
442}
443
444/// set39 color scheme from <https://graphviz.org/doc/info/colors.html>
445const COLORS: &[&str] = &[
446    "white", // pattern starts at 1
447    "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
448    "#d9d9d9",
449];
450
451struct Prefixed<T>(Handle<T>);
452
453impl core::fmt::Display for Prefixed<crate::Expression> {
454    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
455        self.0.write_prefixed(f, "e")
456    }
457}
458
459impl core::fmt::Display for Prefixed<crate::LocalVariable> {
460    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
461        self.0.write_prefixed(f, "l")
462    }
463}
464
465impl core::fmt::Display for Prefixed<crate::GlobalVariable> {
466    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
467        self.0.write_prefixed(f, "g")
468    }
469}
470
471impl core::fmt::Display for Prefixed<crate::Function> {
472    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
473        self.0.write_prefixed(f, "f")
474    }
475}
476
477fn write_fun(
478    output: &mut String,
479    prefix: String,
480    fun: &crate::Function,
481    info: Option<&FunctionInfo>,
482    options: &Options,
483) -> Result<(), FmtError> {
484    writeln!(output, "\t\tnode [ style=filled ]")?;
485
486    if !options.cfg_only {
487        for (handle, var) in fun.local_variables.iter() {
488            writeln!(
489                output,
490                "\t\t{}_{} [ shape=hexagon label=\"{:?} '{}'\" ]",
491                prefix,
492                Prefixed(handle),
493                handle,
494                name(&var.name),
495            )?;
496        }
497
498        write_function_expressions(output, &prefix, fun, info)?;
499    }
500
501    let mut sg = StatementGraph::default();
502    sg.add(&fun.body, Targets::default());
503    for (index, label) in sg.nodes.into_iter().enumerate() {
504        writeln!(
505            output,
506            "\t\t{prefix}_s{index} [ shape=square label=\"{label}\" ]",
507        )?;
508    }
509    for (from, to, label) in sg.flow {
510        writeln!(
511            output,
512            "\t\t{prefix}_s{from} -> {prefix}_s{to} [ arrowhead=tee label=\"{label}\" ]",
513        )?;
514    }
515    for (from, to, label, color_id) in sg.jumps {
516        writeln!(
517            output,
518            "\t\t{}_s{} -> {}_s{} [ arrowhead=tee style=dashed color=\"{}\" label=\"{}\" ]",
519            prefix, from, prefix, to, COLORS[color_id], label,
520        )?;
521    }
522
523    if !options.cfg_only {
524        for (to, expr, label) in sg.dependencies {
525            writeln!(
526                output,
527                "\t\t{}_{} -> {}_s{} [ label=\"{}\" ]",
528                prefix,
529                Prefixed(expr),
530                prefix,
531                to,
532                label,
533            )?;
534        }
535        for (from, to) in sg.emits {
536            writeln!(
537                output,
538                "\t\t{}_s{} -> {}_{} [ style=dotted ]",
539                prefix,
540                from,
541                prefix,
542                Prefixed(to),
543            )?;
544        }
545    }
546
547    assert!(sg.calls.is_empty());
548    for (from, function) in sg.calls {
549        writeln!(
550            output,
551            "\t\t{}_s{} -> {}_s0",
552            prefix,
553            from,
554            Prefixed(function),
555        )?;
556    }
557
558    Ok(())
559}
560
561fn write_function_expressions(
562    output: &mut String,
563    prefix: &str,
564    fun: &crate::Function,
565    info: Option<&FunctionInfo>,
566) -> Result<(), FmtError> {
567    enum Payload<'a> {
568        Arguments(&'a [Handle<crate::Expression>]),
569        Local(Handle<crate::LocalVariable>),
570        Global(Handle<crate::GlobalVariable>),
571    }
572
573    let mut edges = crate::FastHashMap::<&str, _>::default();
574    let mut payload = None;
575    for (handle, expression) in fun.expressions.iter() {
576        use crate::Expression as E;
577        let (label, color_id) = match *expression {
578            E::Literal(_) => ("Literal".into(), 2),
579            E::Constant(_) => ("Constant".into(), 2),
580            E::Override(_) => ("Override".into(), 2),
581            E::ZeroValue(_) => ("ZeroValue".into(), 2),
582            E::Compose { ref components, .. } => {
583                payload = Some(Payload::Arguments(components));
584                ("Compose".into(), 3)
585            }
586            E::Access { base, index } => {
587                edges.insert("base", base);
588                edges.insert("index", index);
589                ("Access".into(), 1)
590            }
591            E::AccessIndex { base, index } => {
592                edges.insert("base", base);
593                (format!("AccessIndex[{index}]").into(), 1)
594            }
595            E::Splat { size, value } => {
596                edges.insert("value", value);
597                (format!("Splat{size:?}").into(), 3)
598            }
599            E::Swizzle {
600                size,
601                vector,
602                pattern,
603            } => {
604                edges.insert("vector", vector);
605                (format!("Swizzle{:?}", &pattern[..size as usize]).into(), 3)
606            }
607            E::FunctionArgument(index) => (format!("Argument[{index}]").into(), 1),
608            E::GlobalVariable(h) => {
609                payload = Some(Payload::Global(h));
610                ("Global".into(), 2)
611            }
612            E::LocalVariable(h) => {
613                payload = Some(Payload::Local(h));
614                ("Local".into(), 1)
615            }
616            E::Load { pointer } => {
617                edges.insert("pointer", pointer);
618                ("Load".into(), 4)
619            }
620            E::ImageSample {
621                image,
622                sampler,
623                gather,
624                coordinate,
625                array_index,
626                offset: _,
627                level,
628                depth_ref,
629                clamp_to_edge: _,
630            } => {
631                edges.insert("image", image);
632                edges.insert("sampler", sampler);
633                edges.insert("coordinate", coordinate);
634                if let Some(expr) = array_index {
635                    edges.insert("array_index", expr);
636                }
637                match level {
638                    crate::SampleLevel::Auto => {}
639                    crate::SampleLevel::Zero => {}
640                    crate::SampleLevel::Exact(expr) => {
641                        edges.insert("level", expr);
642                    }
643                    crate::SampleLevel::Bias(expr) => {
644                        edges.insert("bias", expr);
645                    }
646                    crate::SampleLevel::Gradient { x, y } => {
647                        edges.insert("grad_x", x);
648                        edges.insert("grad_y", y);
649                    }
650                }
651                if let Some(expr) = depth_ref {
652                    edges.insert("depth_ref", expr);
653                }
654                let string = match gather {
655                    Some(component) => Cow::Owned(format!("ImageGather{component:?}")),
656                    _ => Cow::Borrowed("ImageSample"),
657                };
658                (string, 5)
659            }
660            E::ImageLoad {
661                image,
662                coordinate,
663                array_index,
664                sample,
665                level,
666            } => {
667                edges.insert("image", image);
668                edges.insert("coordinate", coordinate);
669                if let Some(expr) = array_index {
670                    edges.insert("array_index", expr);
671                }
672                if let Some(sample) = sample {
673                    edges.insert("sample", sample);
674                }
675                if let Some(level) = level {
676                    edges.insert("level", level);
677                }
678                ("ImageLoad".into(), 5)
679            }
680            E::ImageQuery { image, query } => {
681                edges.insert("image", image);
682                let args = match query {
683                    crate::ImageQuery::Size { level } => {
684                        if let Some(expr) = level {
685                            edges.insert("level", expr);
686                        }
687                        Cow::from("ImageSize")
688                    }
689                    _ => Cow::Owned(format!("{query:?}")),
690                };
691                (args, 7)
692            }
693            E::Unary { op, expr } => {
694                edges.insert("expr", expr);
695                (format!("{op:?}").into(), 6)
696            }
697            E::Binary { op, left, right } => {
698                edges.insert("left", left);
699                edges.insert("right", right);
700                (format!("{op:?}").into(), 6)
701            }
702            E::Select {
703                condition,
704                accept,
705                reject,
706            } => {
707                edges.insert("condition", condition);
708                edges.insert("accept", accept);
709                edges.insert("reject", reject);
710                ("Select".into(), 3)
711            }
712            E::Derivative { axis, ctrl, expr } => {
713                edges.insert("", expr);
714                (format!("d{axis:?}{ctrl:?}").into(), 8)
715            }
716            E::Relational { fun, argument } => {
717                edges.insert("arg", argument);
718                (format!("{fun:?}").into(), 6)
719            }
720            E::Math {
721                fun,
722                arg,
723                arg1,
724                arg2,
725                arg3,
726            } => {
727                edges.insert("arg", arg);
728                if let Some(expr) = arg1 {
729                    edges.insert("arg1", expr);
730                }
731                if let Some(expr) = arg2 {
732                    edges.insert("arg2", expr);
733                }
734                if let Some(expr) = arg3 {
735                    edges.insert("arg3", expr);
736                }
737                (format!("{fun:?}").into(), 7)
738            }
739            E::As {
740                kind,
741                expr,
742                convert,
743            } => {
744                edges.insert("", expr);
745                let string = match convert {
746                    Some(width) => format!("Convert<{kind:?},{width}>"),
747                    None => format!("Bitcast<{kind:?}>"),
748                };
749                (string.into(), 3)
750            }
751            E::CallResult(_function) => ("CallResult".into(), 4),
752            E::AtomicResult { .. } => ("AtomicResult".into(), 4),
753            E::WorkGroupUniformLoadResult { .. } => ("WorkGroupUniformLoadResult".into(), 4),
754            E::ArrayLength(expr) => {
755                edges.insert("", expr);
756                ("ArrayLength".into(), 7)
757            }
758            E::RayQueryProceedResult => ("rayQueryProceedResult".into(), 4),
759            E::RayQueryGetIntersection { query, committed } => {
760                edges.insert("", query);
761                let ty = if committed { "Committed" } else { "Candidate" };
762                (format!("rayQueryGet{ty}Intersection").into(), 4)
763            }
764            E::SubgroupBallotResult => ("SubgroupBallotResult".into(), 4),
765            E::SubgroupOperationResult { .. } => ("SubgroupOperationResult".into(), 4),
766            E::RayQueryVertexPositions { query, committed } => {
767                edges.insert("", query);
768                let ty = if committed { "Committed" } else { "Candidate" };
769                (format!("get{ty}HitVertexPositions").into(), 4)
770            }
771            E::CooperativeLoad { ref data, .. } => {
772                edges.insert("pointer", data.pointer);
773                edges.insert("stride", data.stride);
774                let suffix = if data.row_major { "T " } else { "" };
775                (format!("coopLoad{suffix}").into(), 4)
776            }
777            E::CooperativeMultiplyAdd { a, b, c } => {
778                edges.insert("a", a);
779                edges.insert("b", b);
780                edges.insert("c", c);
781                ("cooperativeMultiplyAdd".into(), 4)
782            }
783        };
784
785        // give uniform expressions an outline
786        let color_attr = match info {
787            Some(info) if info[handle].uniformity.non_uniform_result.is_none() => "fillcolor",
788            _ => "color",
789        };
790        writeln!(
791            output,
792            "\t\t{}_{} [ {}=\"{}\" label=\"{:?} {}\" ]",
793            prefix,
794            Prefixed(handle),
795            color_attr,
796            COLORS[color_id],
797            handle,
798            label,
799        )?;
800
801        for (key, edge) in edges.drain() {
802            writeln!(
803                output,
804                "\t\t{}_{} -> {}_{} [ label=\"{}\" ]",
805                prefix,
806                Prefixed(edge),
807                prefix,
808                Prefixed(handle),
809                key,
810            )?;
811        }
812        match payload.take() {
813            Some(Payload::Arguments(list)) => {
814                write!(output, "\t\t{{")?;
815                for &comp in list {
816                    write!(output, " {}_{}", prefix, Prefixed(comp))?;
817                }
818                writeln!(output, " }} -> {}_{}", prefix, Prefixed(handle))?;
819            }
820            Some(Payload::Local(h)) => {
821                writeln!(
822                    output,
823                    "\t\t{}_{} -> {}_{}",
824                    prefix,
825                    Prefixed(h),
826                    prefix,
827                    Prefixed(handle),
828                )?;
829            }
830            Some(Payload::Global(h)) => {
831                writeln!(
832                    output,
833                    "\t\t{} -> {}_{} [fillcolor=gray]",
834                    Prefixed(h),
835                    prefix,
836                    Prefixed(handle),
837                )?;
838            }
839            None => {}
840        }
841    }
842
843    Ok(())
844}
845
846/// Write shader module to a [`String`].
847pub fn write(
848    module: &crate::Module,
849    mod_info: Option<&ModuleInfo>,
850    options: Options,
851) -> Result<String, FmtError> {
852    use core::fmt::Write as _;
853
854    let mut output = String::new();
855    output += "digraph Module {\n";
856
857    if !options.cfg_only {
858        writeln!(output, "\tsubgraph cluster_globals {{")?;
859        writeln!(output, "\t\tlabel=\"Globals\"")?;
860        for (handle, var) in module.global_variables.iter() {
861            writeln!(
862                output,
863                "\t\t{} [ shape=hexagon label=\"{:?} {:?}/'{}'\" ]",
864                Prefixed(handle),
865                handle,
866                var.space,
867                name(&var.name),
868            )?;
869        }
870        writeln!(output, "\t}}")?;
871    }
872
873    for (handle, fun) in module.functions.iter() {
874        let prefix = Prefixed(handle).to_string();
875        writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
876        writeln!(
877            output,
878            "\t\tlabel=\"Function{:?}/'{}'\"",
879            handle,
880            name(&fun.name)
881        )?;
882        let info = mod_info.map(|a| &a[handle]);
883        write_fun(&mut output, prefix, fun, info, &options)?;
884        writeln!(output, "\t}}")?;
885    }
886    for (ep_index, ep) in module.entry_points.iter().enumerate() {
887        let prefix = format!("ep{ep_index}");
888        writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
889        writeln!(output, "\t\tlabel=\"{:?}/'{}'\"", ep.stage, ep.name)?;
890        let info = mod_info.map(|a| a.get_entry_point(ep_index));
891        write_fun(&mut output, prefix, &ep.function, info, &options)?;
892        writeln!(output, "\t}}")?;
893    }
894
895    output += "}\n";
896    Ok(output)
897}