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            };
417            // Set the last node to the merge node
418            last_node = merge_id;
419        }
420        (root, last_node)
421    }
422}
423
424fn name(option: &Option<String>) -> &str {
425    option.as_deref().unwrap_or_default()
426}
427
428/// set39 color scheme from <https://graphviz.org/doc/info/colors.html>
429const COLORS: &[&str] = &[
430    "white", // pattern starts at 1
431    "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
432    "#d9d9d9",
433];
434
435struct Prefixed<T>(Handle<T>);
436
437impl core::fmt::Display for Prefixed<crate::Expression> {
438    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
439        self.0.write_prefixed(f, "e")
440    }
441}
442
443impl core::fmt::Display for Prefixed<crate::LocalVariable> {
444    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
445        self.0.write_prefixed(f, "l")
446    }
447}
448
449impl core::fmt::Display for Prefixed<crate::GlobalVariable> {
450    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
451        self.0.write_prefixed(f, "g")
452    }
453}
454
455impl core::fmt::Display for Prefixed<crate::Function> {
456    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
457        self.0.write_prefixed(f, "f")
458    }
459}
460
461fn write_fun(
462    output: &mut String,
463    prefix: String,
464    fun: &crate::Function,
465    info: Option<&FunctionInfo>,
466    options: &Options,
467) -> Result<(), FmtError> {
468    writeln!(output, "\t\tnode [ style=filled ]")?;
469
470    if !options.cfg_only {
471        for (handle, var) in fun.local_variables.iter() {
472            writeln!(
473                output,
474                "\t\t{}_{} [ shape=hexagon label=\"{:?} '{}'\" ]",
475                prefix,
476                Prefixed(handle),
477                handle,
478                name(&var.name),
479            )?;
480        }
481
482        write_function_expressions(output, &prefix, fun, info)?;
483    }
484
485    let mut sg = StatementGraph::default();
486    sg.add(&fun.body, Targets::default());
487    for (index, label) in sg.nodes.into_iter().enumerate() {
488        writeln!(
489            output,
490            "\t\t{prefix}_s{index} [ shape=square label=\"{label}\" ]",
491        )?;
492    }
493    for (from, to, label) in sg.flow {
494        writeln!(
495            output,
496            "\t\t{prefix}_s{from} -> {prefix}_s{to} [ arrowhead=tee label=\"{label}\" ]",
497        )?;
498    }
499    for (from, to, label, color_id) in sg.jumps {
500        writeln!(
501            output,
502            "\t\t{}_s{} -> {}_s{} [ arrowhead=tee style=dashed color=\"{}\" label=\"{}\" ]",
503            prefix, from, prefix, to, COLORS[color_id], label,
504        )?;
505    }
506
507    if !options.cfg_only {
508        for (to, expr, label) in sg.dependencies {
509            writeln!(
510                output,
511                "\t\t{}_{} -> {}_s{} [ label=\"{}\" ]",
512                prefix,
513                Prefixed(expr),
514                prefix,
515                to,
516                label,
517            )?;
518        }
519        for (from, to) in sg.emits {
520            writeln!(
521                output,
522                "\t\t{}_s{} -> {}_{} [ style=dotted ]",
523                prefix,
524                from,
525                prefix,
526                Prefixed(to),
527            )?;
528        }
529    }
530
531    assert!(sg.calls.is_empty());
532    for (from, function) in sg.calls {
533        writeln!(
534            output,
535            "\t\t{}_s{} -> {}_s0",
536            prefix,
537            from,
538            Prefixed(function),
539        )?;
540    }
541
542    Ok(())
543}
544
545fn write_function_expressions(
546    output: &mut String,
547    prefix: &str,
548    fun: &crate::Function,
549    info: Option<&FunctionInfo>,
550) -> Result<(), FmtError> {
551    enum Payload<'a> {
552        Arguments(&'a [Handle<crate::Expression>]),
553        Local(Handle<crate::LocalVariable>),
554        Global(Handle<crate::GlobalVariable>),
555    }
556
557    let mut edges = crate::FastHashMap::<&str, _>::default();
558    let mut payload = None;
559    for (handle, expression) in fun.expressions.iter() {
560        use crate::Expression as E;
561        let (label, color_id) = match *expression {
562            E::Literal(_) => ("Literal".into(), 2),
563            E::Constant(_) => ("Constant".into(), 2),
564            E::Override(_) => ("Override".into(), 2),
565            E::ZeroValue(_) => ("ZeroValue".into(), 2),
566            E::Compose { ref components, .. } => {
567                payload = Some(Payload::Arguments(components));
568                ("Compose".into(), 3)
569            }
570            E::Access { base, index } => {
571                edges.insert("base", base);
572                edges.insert("index", index);
573                ("Access".into(), 1)
574            }
575            E::AccessIndex { base, index } => {
576                edges.insert("base", base);
577                (format!("AccessIndex[{index}]").into(), 1)
578            }
579            E::Splat { size, value } => {
580                edges.insert("value", value);
581                (format!("Splat{size:?}").into(), 3)
582            }
583            E::Swizzle {
584                size,
585                vector,
586                pattern,
587            } => {
588                edges.insert("vector", vector);
589                (format!("Swizzle{:?}", &pattern[..size as usize]).into(), 3)
590            }
591            E::FunctionArgument(index) => (format!("Argument[{index}]").into(), 1),
592            E::GlobalVariable(h) => {
593                payload = Some(Payload::Global(h));
594                ("Global".into(), 2)
595            }
596            E::LocalVariable(h) => {
597                payload = Some(Payload::Local(h));
598                ("Local".into(), 1)
599            }
600            E::Load { pointer } => {
601                edges.insert("pointer", pointer);
602                ("Load".into(), 4)
603            }
604            E::ImageSample {
605                image,
606                sampler,
607                gather,
608                coordinate,
609                array_index,
610                offset: _,
611                level,
612                depth_ref,
613                clamp_to_edge: _,
614            } => {
615                edges.insert("image", image);
616                edges.insert("sampler", sampler);
617                edges.insert("coordinate", coordinate);
618                if let Some(expr) = array_index {
619                    edges.insert("array_index", expr);
620                }
621                match level {
622                    crate::SampleLevel::Auto => {}
623                    crate::SampleLevel::Zero => {}
624                    crate::SampleLevel::Exact(expr) => {
625                        edges.insert("level", expr);
626                    }
627                    crate::SampleLevel::Bias(expr) => {
628                        edges.insert("bias", expr);
629                    }
630                    crate::SampleLevel::Gradient { x, y } => {
631                        edges.insert("grad_x", x);
632                        edges.insert("grad_y", y);
633                    }
634                }
635                if let Some(expr) = depth_ref {
636                    edges.insert("depth_ref", expr);
637                }
638                let string = match gather {
639                    Some(component) => Cow::Owned(format!("ImageGather{component:?}")),
640                    _ => Cow::Borrowed("ImageSample"),
641                };
642                (string, 5)
643            }
644            E::ImageLoad {
645                image,
646                coordinate,
647                array_index,
648                sample,
649                level,
650            } => {
651                edges.insert("image", image);
652                edges.insert("coordinate", coordinate);
653                if let Some(expr) = array_index {
654                    edges.insert("array_index", expr);
655                }
656                if let Some(sample) = sample {
657                    edges.insert("sample", sample);
658                }
659                if let Some(level) = level {
660                    edges.insert("level", level);
661                }
662                ("ImageLoad".into(), 5)
663            }
664            E::ImageQuery { image, query } => {
665                edges.insert("image", image);
666                let args = match query {
667                    crate::ImageQuery::Size { level } => {
668                        if let Some(expr) = level {
669                            edges.insert("level", expr);
670                        }
671                        Cow::from("ImageSize")
672                    }
673                    _ => Cow::Owned(format!("{query:?}")),
674                };
675                (args, 7)
676            }
677            E::Unary { op, expr } => {
678                edges.insert("expr", expr);
679                (format!("{op:?}").into(), 6)
680            }
681            E::Binary { op, left, right } => {
682                edges.insert("left", left);
683                edges.insert("right", right);
684                (format!("{op:?}").into(), 6)
685            }
686            E::Select {
687                condition,
688                accept,
689                reject,
690            } => {
691                edges.insert("condition", condition);
692                edges.insert("accept", accept);
693                edges.insert("reject", reject);
694                ("Select".into(), 3)
695            }
696            E::Derivative { axis, ctrl, expr } => {
697                edges.insert("", expr);
698                (format!("d{axis:?}{ctrl:?}").into(), 8)
699            }
700            E::Relational { fun, argument } => {
701                edges.insert("arg", argument);
702                (format!("{fun:?}").into(), 6)
703            }
704            E::Math {
705                fun,
706                arg,
707                arg1,
708                arg2,
709                arg3,
710            } => {
711                edges.insert("arg", arg);
712                if let Some(expr) = arg1 {
713                    edges.insert("arg1", expr);
714                }
715                if let Some(expr) = arg2 {
716                    edges.insert("arg2", expr);
717                }
718                if let Some(expr) = arg3 {
719                    edges.insert("arg3", expr);
720                }
721                (format!("{fun:?}").into(), 7)
722            }
723            E::As {
724                kind,
725                expr,
726                convert,
727            } => {
728                edges.insert("", expr);
729                let string = match convert {
730                    Some(width) => format!("Convert<{kind:?},{width}>"),
731                    None => format!("Bitcast<{kind:?}>"),
732                };
733                (string.into(), 3)
734            }
735            E::CallResult(_function) => ("CallResult".into(), 4),
736            E::AtomicResult { .. } => ("AtomicResult".into(), 4),
737            E::WorkGroupUniformLoadResult { .. } => ("WorkGroupUniformLoadResult".into(), 4),
738            E::ArrayLength(expr) => {
739                edges.insert("", expr);
740                ("ArrayLength".into(), 7)
741            }
742            E::RayQueryProceedResult => ("rayQueryProceedResult".into(), 4),
743            E::RayQueryGetIntersection { query, committed } => {
744                edges.insert("", query);
745                let ty = if committed { "Committed" } else { "Candidate" };
746                (format!("rayQueryGet{ty}Intersection").into(), 4)
747            }
748            E::SubgroupBallotResult => ("SubgroupBallotResult".into(), 4),
749            E::SubgroupOperationResult { .. } => ("SubgroupOperationResult".into(), 4),
750            E::RayQueryVertexPositions { query, committed } => {
751                edges.insert("", query);
752                let ty = if committed { "Committed" } else { "Candidate" };
753                (format!("get{ty}HitVertexPositions").into(), 4)
754            }
755            E::CooperativeLoad { ref data, .. } => {
756                edges.insert("pointer", data.pointer);
757                edges.insert("stride", data.stride);
758                let suffix = if data.row_major { "T " } else { "" };
759                (format!("coopLoad{suffix}").into(), 4)
760            }
761            E::CooperativeMultiplyAdd { a, b, c } => {
762                edges.insert("a", a);
763                edges.insert("b", b);
764                edges.insert("c", c);
765                ("cooperativeMultiplyAdd".into(), 4)
766            }
767        };
768
769        // give uniform expressions an outline
770        let color_attr = match info {
771            Some(info) if info[handle].uniformity.non_uniform_result.is_none() => "fillcolor",
772            _ => "color",
773        };
774        writeln!(
775            output,
776            "\t\t{}_{} [ {}=\"{}\" label=\"{:?} {}\" ]",
777            prefix,
778            Prefixed(handle),
779            color_attr,
780            COLORS[color_id],
781            handle,
782            label,
783        )?;
784
785        for (key, edge) in edges.drain() {
786            writeln!(
787                output,
788                "\t\t{}_{} -> {}_{} [ label=\"{}\" ]",
789                prefix,
790                Prefixed(edge),
791                prefix,
792                Prefixed(handle),
793                key,
794            )?;
795        }
796        match payload.take() {
797            Some(Payload::Arguments(list)) => {
798                write!(output, "\t\t{{")?;
799                for &comp in list {
800                    write!(output, " {}_{}", prefix, Prefixed(comp))?;
801                }
802                writeln!(output, " }} -> {}_{}", prefix, Prefixed(handle))?;
803            }
804            Some(Payload::Local(h)) => {
805                writeln!(
806                    output,
807                    "\t\t{}_{} -> {}_{}",
808                    prefix,
809                    Prefixed(h),
810                    prefix,
811                    Prefixed(handle),
812                )?;
813            }
814            Some(Payload::Global(h)) => {
815                writeln!(
816                    output,
817                    "\t\t{} -> {}_{} [fillcolor=gray]",
818                    Prefixed(h),
819                    prefix,
820                    Prefixed(handle),
821                )?;
822            }
823            None => {}
824        }
825    }
826
827    Ok(())
828}
829
830/// Write shader module to a [`String`].
831pub fn write(
832    module: &crate::Module,
833    mod_info: Option<&ModuleInfo>,
834    options: Options,
835) -> Result<String, FmtError> {
836    use core::fmt::Write as _;
837
838    let mut output = String::new();
839    output += "digraph Module {\n";
840
841    if !options.cfg_only {
842        writeln!(output, "\tsubgraph cluster_globals {{")?;
843        writeln!(output, "\t\tlabel=\"Globals\"")?;
844        for (handle, var) in module.global_variables.iter() {
845            writeln!(
846                output,
847                "\t\t{} [ shape=hexagon label=\"{:?} {:?}/'{}'\" ]",
848                Prefixed(handle),
849                handle,
850                var.space,
851                name(&var.name),
852            )?;
853        }
854        writeln!(output, "\t}}")?;
855    }
856
857    for (handle, fun) in module.functions.iter() {
858        let prefix = Prefixed(handle).to_string();
859        writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
860        writeln!(
861            output,
862            "\t\tlabel=\"Function{:?}/'{}'\"",
863            handle,
864            name(&fun.name)
865        )?;
866        let info = mod_info.map(|a| &a[handle]);
867        write_fun(&mut output, prefix, fun, info, &options)?;
868        writeln!(output, "\t}}")?;
869    }
870    for (ep_index, ep) in module.entry_points.iter().enumerate() {
871        let prefix = format!("ep{ep_index}");
872        writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
873        writeln!(output, "\t\tlabel=\"{:?}/'{}'\"", ep.stage, ep.name)?;
874        let info = mod_info.map(|a| a.get_entry_point(ep_index));
875        write_fun(&mut output, prefix, &ep.function, info, &options)?;
876        writeln!(output, "\t}}")?;
877    }
878
879    output += "}\n";
880    Ok(output)
881}