Skip to main content

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, Debug, 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                        crate::RayQueryFunction::Begin => "RayQueryVariableUsageBegins",
309                    }
310                }
311                S::SubgroupBallot { result, predicate } => {
312                    if let Some(predicate) = predicate {
313                        self.dependencies.push((id, predicate, "predicate"));
314                    }
315                    self.emits.push((id, result));
316                    "SubgroupBallot"
317                }
318                S::SubgroupCollectiveOperation {
319                    op,
320                    collective_op,
321                    argument,
322                    result,
323                } => {
324                    self.dependencies.push((id, argument, "arg"));
325                    self.emits.push((id, result));
326                    match (collective_op, op) {
327                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => {
328                            "SubgroupAll"
329                        }
330                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => {
331                            "SubgroupAny"
332                        }
333                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => {
334                            "SubgroupAdd"
335                        }
336                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => {
337                            "SubgroupMul"
338                        }
339                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => {
340                            "SubgroupMax"
341                        }
342                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => {
343                            "SubgroupMin"
344                        }
345                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => {
346                            "SubgroupAnd"
347                        }
348                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => {
349                            "SubgroupOr"
350                        }
351                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => {
352                            "SubgroupXor"
353                        }
354                        (
355                            crate::CollectiveOperation::ExclusiveScan,
356                            crate::SubgroupOperation::Add,
357                        ) => "SubgroupExclusiveAdd",
358                        (
359                            crate::CollectiveOperation::ExclusiveScan,
360                            crate::SubgroupOperation::Mul,
361                        ) => "SubgroupExclusiveMul",
362                        (
363                            crate::CollectiveOperation::InclusiveScan,
364                            crate::SubgroupOperation::Add,
365                        ) => "SubgroupInclusiveAdd",
366                        (
367                            crate::CollectiveOperation::InclusiveScan,
368                            crate::SubgroupOperation::Mul,
369                        ) => "SubgroupInclusiveMul",
370                        _ => unimplemented!(),
371                    }
372                }
373                S::SubgroupGather {
374                    mode,
375                    argument,
376                    result,
377                } => {
378                    match mode {
379                        crate::GatherMode::BroadcastFirst => {}
380                        crate::GatherMode::Broadcast(index)
381                        | crate::GatherMode::Shuffle(index)
382                        | crate::GatherMode::ShuffleDown(index)
383                        | crate::GatherMode::ShuffleUp(index)
384                        | crate::GatherMode::ShuffleXor(index)
385                        | crate::GatherMode::QuadBroadcast(index) => {
386                            self.dependencies.push((id, index, "index"))
387                        }
388                        crate::GatherMode::QuadSwap(_) => {}
389                    }
390                    self.dependencies.push((id, argument, "arg"));
391                    self.emits.push((id, result));
392                    match mode {
393                        crate::GatherMode::BroadcastFirst => "SubgroupBroadcastFirst",
394                        crate::GatherMode::Broadcast(_) => "SubgroupBroadcast",
395                        crate::GatherMode::Shuffle(_) => "SubgroupShuffle",
396                        crate::GatherMode::ShuffleDown(_) => "SubgroupShuffleDown",
397                        crate::GatherMode::ShuffleUp(_) => "SubgroupShuffleUp",
398                        crate::GatherMode::ShuffleXor(_) => "SubgroupShuffleXor",
399                        crate::GatherMode::QuadBroadcast(_) => "SubgroupQuadBroadcast",
400                        crate::GatherMode::QuadSwap(direction) => match direction {
401                            crate::Direction::X => "SubgroupQuadSwapX",
402                            crate::Direction::Y => "SubgroupQuadSwapY",
403                            crate::Direction::Diagonal => "SubgroupQuadSwapDiagonal",
404                        },
405                    }
406                }
407                S::CooperativeStore { target, data } => {
408                    self.dependencies.push((id, target, "target"));
409                    self.dependencies.push((id, data.pointer, "pointer"));
410                    self.dependencies.push((id, data.stride, "stride"));
411                    if data.row_major {
412                        "CoopStoreT"
413                    } else {
414                        "CoopStore"
415                    }
416                }
417                S::RayPipelineFunction(func) => match func {
418                    crate::RayPipelineFunction::TraceRay {
419                        acceleration_structure,
420                        descriptor,
421                        payload,
422                    } => {
423                        self.dependencies.push((
424                            id,
425                            acceleration_structure,
426                            "acceleration_structure",
427                        ));
428                        self.dependencies.push((id, descriptor, "descriptor"));
429                        self.dependencies.push((id, payload, "payload"));
430                        "TraceRay"
431                    }
432                },
433                S::DebugPrintf {
434                    format: _,
435                    ref arguments,
436                } => {
437                    for &expr in arguments {
438                        self.dependencies.push((id, expr, "arg"));
439                    }
440                    "DebugPrintf"
441                }
442            };
443            // Set the last node to the merge node
444            last_node = merge_id;
445        }
446        (root, last_node)
447    }
448}
449
450fn name(option: &Option<String>) -> &str {
451    option.as_deref().unwrap_or_default()
452}
453
454/// set39 color scheme from <https://graphviz.org/doc/info/colors.html>
455const COLORS: &[&str] = &[
456    "white", // pattern starts at 1
457    "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
458    "#d9d9d9",
459];
460
461struct Prefixed<T>(Handle<T>);
462
463impl core::fmt::Display for Prefixed<crate::Expression> {
464    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
465        self.0.write_prefixed(f, "e")
466    }
467}
468
469impl core::fmt::Display for Prefixed<crate::LocalVariable> {
470    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
471        self.0.write_prefixed(f, "l")
472    }
473}
474
475impl core::fmt::Display for Prefixed<crate::GlobalVariable> {
476    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
477        self.0.write_prefixed(f, "g")
478    }
479}
480
481impl core::fmt::Display for Prefixed<crate::Function> {
482    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
483        self.0.write_prefixed(f, "f")
484    }
485}
486
487fn write_fun(
488    output: &mut String,
489    prefix: String,
490    fun: &crate::Function,
491    info: Option<&FunctionInfo>,
492    options: &Options,
493) -> Result<(), FmtError> {
494    writeln!(output, "\t\tnode [ style=filled ]")?;
495
496    if !options.cfg_only {
497        for (handle, var) in fun.local_variables.iter() {
498            writeln!(
499                output,
500                "\t\t{}_{} [ shape=hexagon label=\"{:?} '{}'\" ]",
501                prefix,
502                Prefixed(handle),
503                handle,
504                name(&var.name),
505            )?;
506        }
507
508        write_function_expressions(output, &prefix, fun, info)?;
509    }
510
511    let mut sg = StatementGraph::default();
512    sg.add(&fun.body, Targets::default());
513    for (index, label) in sg.nodes.into_iter().enumerate() {
514        writeln!(
515            output,
516            "\t\t{prefix}_s{index} [ shape=square label=\"{label}\" ]",
517        )?;
518    }
519    for (from, to, label) in sg.flow {
520        writeln!(
521            output,
522            "\t\t{prefix}_s{from} -> {prefix}_s{to} [ arrowhead=tee label=\"{label}\" ]",
523        )?;
524    }
525    for (from, to, label, color_id) in sg.jumps {
526        writeln!(
527            output,
528            "\t\t{}_s{} -> {}_s{} [ arrowhead=tee style=dashed color=\"{}\" label=\"{}\" ]",
529            prefix, from, prefix, to, COLORS[color_id], label,
530        )?;
531    }
532
533    if !options.cfg_only {
534        for (to, expr, label) in sg.dependencies {
535            writeln!(
536                output,
537                "\t\t{}_{} -> {}_s{} [ label=\"{}\" ]",
538                prefix,
539                Prefixed(expr),
540                prefix,
541                to,
542                label,
543            )?;
544        }
545        for (from, to) in sg.emits {
546            writeln!(
547                output,
548                "\t\t{}_s{} -> {}_{} [ style=dotted ]",
549                prefix,
550                from,
551                prefix,
552                Prefixed(to),
553            )?;
554        }
555    }
556
557    assert!(sg.calls.is_empty());
558    for (from, function) in sg.calls {
559        writeln!(
560            output,
561            "\t\t{}_s{} -> {}_s0",
562            prefix,
563            from,
564            Prefixed(function),
565        )?;
566    }
567
568    Ok(())
569}
570
571fn write_function_expressions(
572    output: &mut String,
573    prefix: &str,
574    fun: &crate::Function,
575    info: Option<&FunctionInfo>,
576) -> Result<(), FmtError> {
577    enum Payload<'a> {
578        Arguments(&'a [Handle<crate::Expression>]),
579        Local(Handle<crate::LocalVariable>),
580        Global(Handle<crate::GlobalVariable>),
581    }
582
583    let mut edges = crate::FastHashMap::<&str, _>::default();
584    let mut payload = None;
585    for (handle, expression) in fun.expressions.iter() {
586        use crate::Expression as E;
587        let (label, color_id) = match *expression {
588            E::Literal(_) => ("Literal".into(), 2),
589            E::Constant(_) => ("Constant".into(), 2),
590            E::Override(_) => ("Override".into(), 2),
591            E::ZeroValue(_) => ("ZeroValue".into(), 2),
592            E::Compose { ref components, .. } => {
593                payload = Some(Payload::Arguments(components));
594                ("Compose".into(), 3)
595            }
596            E::Access { base, index } => {
597                edges.insert("base", base);
598                edges.insert("index", index);
599                ("Access".into(), 1)
600            }
601            E::AccessIndex { base, index } => {
602                edges.insert("base", base);
603                (format!("AccessIndex[{index}]").into(), 1)
604            }
605            E::Splat { size, value } => {
606                edges.insert("value", value);
607                (format!("Splat{size:?}").into(), 3)
608            }
609            E::Swizzle {
610                size,
611                vector,
612                pattern,
613            } => {
614                edges.insert("vector", vector);
615                (format!("Swizzle{:?}", &pattern[..size as usize]).into(), 3)
616            }
617            E::FunctionArgument(index) => (format!("Argument[{index}]").into(), 1),
618            E::GlobalVariable(h) => {
619                payload = Some(Payload::Global(h));
620                ("Global".into(), 2)
621            }
622            E::LocalVariable(h) => {
623                payload = Some(Payload::Local(h));
624                ("Local".into(), 1)
625            }
626            E::Load { pointer } => {
627                edges.insert("pointer", pointer);
628                ("Load".into(), 4)
629            }
630            E::ImageSample {
631                image,
632                sampler,
633                gather,
634                coordinate,
635                array_index,
636                offset: _,
637                level,
638                depth_ref,
639                clamp_to_edge: _,
640            } => {
641                edges.insert("image", image);
642                edges.insert("sampler", sampler);
643                edges.insert("coordinate", coordinate);
644                if let Some(expr) = array_index {
645                    edges.insert("array_index", expr);
646                }
647                match level {
648                    crate::SampleLevel::Auto => {}
649                    crate::SampleLevel::Zero => {}
650                    crate::SampleLevel::Exact(expr) => {
651                        edges.insert("level", expr);
652                    }
653                    crate::SampleLevel::Bias(expr) => {
654                        edges.insert("bias", expr);
655                    }
656                    crate::SampleLevel::Gradient { x, y } => {
657                        edges.insert("grad_x", x);
658                        edges.insert("grad_y", y);
659                    }
660                }
661                if let Some(expr) = depth_ref {
662                    edges.insert("depth_ref", expr);
663                }
664                let string = match gather {
665                    Some(component) => Cow::Owned(format!("ImageGather{component:?}")),
666                    _ => Cow::Borrowed("ImageSample"),
667                };
668                (string, 5)
669            }
670            E::ImageLoad {
671                image,
672                coordinate,
673                array_index,
674                sample,
675                level,
676            } => {
677                edges.insert("image", image);
678                edges.insert("coordinate", coordinate);
679                if let Some(expr) = array_index {
680                    edges.insert("array_index", expr);
681                }
682                if let Some(sample) = sample {
683                    edges.insert("sample", sample);
684                }
685                if let Some(level) = level {
686                    edges.insert("level", level);
687                }
688                ("ImageLoad".into(), 5)
689            }
690            E::ImageQuery { image, query } => {
691                edges.insert("image", image);
692                let args = match query {
693                    crate::ImageQuery::Size { level } => {
694                        if let Some(expr) = level {
695                            edges.insert("level", expr);
696                        }
697                        Cow::from("ImageSize")
698                    }
699                    _ => Cow::Owned(format!("{query:?}")),
700                };
701                (args, 7)
702            }
703            E::Unary { op, expr } => {
704                edges.insert("expr", expr);
705                (format!("{op:?}").into(), 6)
706            }
707            E::Binary { op, left, right } => {
708                edges.insert("left", left);
709                edges.insert("right", right);
710                (format!("{op:?}").into(), 6)
711            }
712            E::Select {
713                condition,
714                accept,
715                reject,
716            } => {
717                edges.insert("condition", condition);
718                edges.insert("accept", accept);
719                edges.insert("reject", reject);
720                ("Select".into(), 3)
721            }
722            E::Derivative { axis, ctrl, expr } => {
723                edges.insert("", expr);
724                (format!("d{axis:?}{ctrl:?}").into(), 8)
725            }
726            E::Relational { fun, argument } => {
727                edges.insert("arg", argument);
728                (format!("{fun:?}").into(), 6)
729            }
730            E::Math {
731                fun,
732                arg,
733                arg1,
734                arg2,
735                arg3,
736            } => {
737                edges.insert("arg", arg);
738                if let Some(expr) = arg1 {
739                    edges.insert("arg1", expr);
740                }
741                if let Some(expr) = arg2 {
742                    edges.insert("arg2", expr);
743                }
744                if let Some(expr) = arg3 {
745                    edges.insert("arg3", expr);
746                }
747                (format!("{fun:?}").into(), 7)
748            }
749            E::As {
750                kind,
751                expr,
752                convert,
753            } => {
754                edges.insert("", expr);
755                let string = match convert {
756                    Some(width) => format!("Convert<{kind:?},{width}>"),
757                    None => format!("Bitcast<{kind:?}>"),
758                };
759                (string.into(), 3)
760            }
761            E::CallResult(_function) => ("CallResult".into(), 4),
762            E::AtomicResult { .. } => ("AtomicResult".into(), 4),
763            E::WorkGroupUniformLoadResult { .. } => ("WorkGroupUniformLoadResult".into(), 4),
764            E::ArrayLength(expr) => {
765                edges.insert("", expr);
766                ("ArrayLength".into(), 7)
767            }
768            E::RayQueryProceedResult => ("rayQueryProceedResult".into(), 4),
769            E::RayQueryGetIntersection { query, committed } => {
770                edges.insert("", query);
771                let ty = if committed { "Committed" } else { "Candidate" };
772                (format!("rayQueryGet{ty}Intersection").into(), 4)
773            }
774            E::SubgroupBallotResult => ("SubgroupBallotResult".into(), 4),
775            E::SubgroupOperationResult { .. } => ("SubgroupOperationResult".into(), 4),
776            E::RayQueryVertexPositions { query, committed } => {
777                edges.insert("", query);
778                let ty = if committed { "Committed" } else { "Candidate" };
779                (format!("get{ty}HitVertexPositions").into(), 4)
780            }
781            E::CooperativeLoad { ref data, .. } => {
782                edges.insert("pointer", data.pointer);
783                edges.insert("stride", data.stride);
784                let suffix = if data.row_major { "T " } else { "" };
785                (format!("coopLoad{suffix}").into(), 4)
786            }
787            E::CooperativeMultiplyAdd { a, b, c } => {
788                edges.insert("a", a);
789                edges.insert("b", b);
790                edges.insert("c", c);
791                ("cooperativeMultiplyAdd".into(), 4)
792            }
793        };
794
795        // give uniform expressions an outline
796        let color_attr = match info {
797            Some(info) if info[handle].uniformity.non_uniform_result.is_none() => "fillcolor",
798            _ => "color",
799        };
800        writeln!(
801            output,
802            "\t\t{}_{} [ {}=\"{}\" label=\"{:?} {}\" ]",
803            prefix,
804            Prefixed(handle),
805            color_attr,
806            COLORS[color_id],
807            handle,
808            label,
809        )?;
810
811        for (key, edge) in edges.drain() {
812            writeln!(
813                output,
814                "\t\t{}_{} -> {}_{} [ label=\"{}\" ]",
815                prefix,
816                Prefixed(edge),
817                prefix,
818                Prefixed(handle),
819                key,
820            )?;
821        }
822        match payload.take() {
823            Some(Payload::Arguments(list)) => {
824                write!(output, "\t\t{{")?;
825                for &comp in list {
826                    write!(output, " {}_{}", prefix, Prefixed(comp))?;
827                }
828                writeln!(output, " }} -> {}_{}", prefix, Prefixed(handle))?;
829            }
830            Some(Payload::Local(h)) => {
831                writeln!(
832                    output,
833                    "\t\t{}_{} -> {}_{}",
834                    prefix,
835                    Prefixed(h),
836                    prefix,
837                    Prefixed(handle),
838                )?;
839            }
840            Some(Payload::Global(h)) => {
841                writeln!(
842                    output,
843                    "\t\t{} -> {}_{} [fillcolor=gray]",
844                    Prefixed(h),
845                    prefix,
846                    Prefixed(handle),
847                )?;
848            }
849            None => {}
850        }
851    }
852
853    Ok(())
854}
855
856/// Write shader module to a [`String`].
857pub fn write(
858    module: &crate::Module,
859    mod_info: Option<&ModuleInfo>,
860    options: Options,
861) -> Result<String, FmtError> {
862    use core::fmt::Write as _;
863
864    let mut output = String::new();
865    output += "digraph Module {\n";
866
867    if !options.cfg_only {
868        writeln!(output, "\tsubgraph cluster_globals {{")?;
869        writeln!(output, "\t\tlabel=\"Globals\"")?;
870        for (handle, var) in module.global_variables.iter() {
871            writeln!(
872                output,
873                "\t\t{} [ shape=hexagon label=\"{:?} {:?}/'{}'\" ]",
874                Prefixed(handle),
875                handle,
876                var.space,
877                name(&var.name),
878            )?;
879        }
880        writeln!(output, "\t}}")?;
881    }
882
883    for (handle, fun) in module.functions.iter() {
884        let prefix = Prefixed(handle).to_string();
885        writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
886        writeln!(
887            output,
888            "\t\tlabel=\"Function{:?}/'{}'\"",
889            handle,
890            name(&fun.name)
891        )?;
892        let info = mod_info.map(|a| &a[handle]);
893        write_fun(&mut output, prefix, fun, info, &options)?;
894        writeln!(output, "\t}}")?;
895    }
896    for (ep_index, ep) in module.entry_points.iter().enumerate() {
897        let prefix = format!("ep{ep_index}");
898        writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
899        writeln!(output, "\t\tlabel=\"{:?}/'{}'\"", ep.stage, ep.name)?;
900        let info = mod_info.map(|a| a.get_entry_point(ep_index));
901        write_fun(&mut output, prefix, &ep.function, info, &options)?;
902        writeln!(output, "\t}}")?;
903    }
904
905    output += "}\n";
906    Ok(output)
907}