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